{
"$type": "site.standard.document",
"content": "---\ntitle: \"Handling Square Webhooks in Phoenix\"\ndescription: \"How to receive and validate Square payment webhooks in an Elixir/Phoenix app,\n with code for HMAC signature verification.\"\ntags:\n - dev\n---\n\nMy\n[brother's cafe](https://the-riotact.com/hot-in-the-suburbs-little-luxton-serves-up-coffee-for-the-community/618459)\ndonates a dollar to the local community centre for every coffee sold, and over\nthe summer I built him a live \"donation counter\" which displays a small\n\"thankyou\" animation when anyone buys a coffee. It's a web app which they run on\nan iPad sitting on the coffee machine.\n\nSince the cafe uses Square for all payments, I was able to set up a\n[webhook](https://developer.squareup.com/docs/webhooks/overview) so the app\nwould receive the \"new sale\" notification ASAP, which should be both\nlower-latency and more efficient than polling.\n\nThe app is basically a single\n[Phoenix LiveView](https://www.phoenixframework.org). Sadly the Square guides\ndon't have examples for Elixir, although it's pretty easy to modify the e.g.\n[Ruby example](https://developer.squareup.com/docs/webhooks/step3validate) code\nto get the job done. If you're looking to do something similar I cobbled\ntogether this info from docs (and a few blogs) and it might help you out to have\nit all in one place.\n\n## Step 1: set up webhook controller (including validation)\n\nIt's important to validate that any incoming webhook is actually from Square, so\nSquare send a special `x-square-hmacsha256-signature` header for\n[validation purposes](https://developer.squareup.com/docs/webhooks/step3validate),\nalthough performing this validation step requires having access to the raw\nrequest body. Thankfully, the\n[\"Custom Body Reader\" section in the `Plug.Parsers` docs](https://hexdocs.pm/plug/Plug.Parsers.html#module-custom-body-reader)\nshows how to do exactly that. Just follow the instructions there.\n\n## Step 2: create webhook controller (including validation)\n\nThe webhook controller module should look something like this (replace the\n`notification_url` and `signature_key` with the right values for your\napp---you'll get your signature key from Square when you register the webhook):\n\n```elixir\ndefmodule MyAppWeb.SquareWebhookController do\n @moduledoc \"\"\"\n Handle webhooks sent from Square.\n \"\"\"\n use MyAppWeb, :controller\n\n @doc \"handle the webhook request\"\n def webhook(conn, params) do\n if is_from_square?(conn) do\n do_stuff(params)\n end\n\n send_response(conn)\n end\n\n @doc \"respond to the Square server (always 200 OK otherwise they'll freak out)\"\n defp send_response(conn) do\n conn\n |> put_resp_content_type(\"text/plain\")\n |> send_resp(200, \"webhook received - thanks.\")\n end\n\n @doc \"returns `true` if webhook came from Square, `false` otherwise\"\n defp is_from_square?(conn) do\n notification_url = \"http://example.com/square/webhook\"\n signature_key = \"WEBHOOK_SIGNATURE_KEY_FROM_SQUARE\"\n {_, signature} = List.keyfind!(conn.req_headers, \"x-square-hmacsha256-signature\", 0)\n\n ## here's where we access the raw request body we put there in the Plug.Parser\n raw_body = Enum.join(conn.assigns.raw_body)\n\n hash =\n :crypto.mac(:hmac, :sha256, signature_key, notification_url <> raw_body)\n |> Base.encode64()\n\n signature == hash\n end\nend\n```\n\n## Step 3: add the endpoint to your router\n\nFinally, add it to to your `router.ex` - something like this, you know the\ndrill.\n\n```elixir\nscope \"/square\", MyAppWeb do\n pipe_through :api\n\n post \"/webhook\", SquareWebhookController, :webhook\nend\n```\n\n## Step 4: subscribe to the webhook\n\nAfter that's all done (and you've deployed your app) you're ready to\n[set up a webhook subscription](https://developer.squareup.com/docs/webhooks/overview).\nFollow the Square docs and Square will start hitting your (deployed) app's\n`https://example.com/square/webhook` endpoint, and your app can do its thing.\n\n:::info[Note]\n\nThese incoming webhook requests _won't_ hit your local development server\nrunning on `localhost`, so testing webhooks is a bit trickier. Since my app runs\non [fly](https://fly.io) it involved a little bit of `IO.inspect`ing in\nproduction and then looking at the logs with `flyctl logs`.\n\n:::\n\nAnd if you live in Canberra, especially in Tuggeranong/Lanyon, maybe\ngo buy a coffee from [Little Luxton](https://www.littleluxton.com) and you can\nsee it for yourself 😊\n",
"createdAt": "2026-05-13T23:14:46.793Z",
"description": "How to receive and validate Square payment webhooks in an Elixir/Phoenix app, with code for HMAC signature verification.",
"path": "/blog/2023/01/22/handling-square-webhooks-in-phoenix",
"publishedAt": "2023-01-22T00:00:00.000Z",
"site": "at://did:plc:tevykrhi4kibtsipzci76d76/site.standard.publication/self",
"tags": [
"dev"
],
"textContent": "How to receive and validate Square payment webhooks in an Elixir/Phoenix app, with code for HMAC signature verification.",
"title": "Handling Square Webhooks in Phoenix"
}