{
  "$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"
}