{"id":16769625,"url":"https://github.com/dbernheisel/sanity_webhook_plug","last_synced_at":"2025-04-10T19:34:26.107Z","repository":{"id":65182542,"uuid":"585633950","full_name":"dbernheisel/sanity_webhook_plug","owner":"dbernheisel","description":"Elixir Sanity Webhook Plug","archived":false,"fork":false,"pushed_at":"2023-03-24T14:50:27.000Z","size":129,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-10T03:08:09.870Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dbernheisel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-05T17:10:57.000Z","updated_at":"2023-03-22T20:48:45.000Z","dependencies_parsed_at":"2024-10-13T06:24:38.803Z","dependency_job_id":null,"html_url":"https://github.com/dbernheisel/sanity_webhook_plug","commit_stats":null,"previous_names":["bitfo/sanity_webhook_plug"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbernheisel%2Fsanity_webhook_plug","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbernheisel%2Fsanity_webhook_plug/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbernheisel%2Fsanity_webhook_plug/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbernheisel%2Fsanity_webhook_plug/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dbernheisel","download_url":"https://codeload.github.com/dbernheisel/sanity_webhook_plug/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248281422,"owners_count":21077423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T06:14:35.492Z","updated_at":"2025-04-10T19:34:26.081Z","avatar_url":"https://github.com/dbernheisel.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- badges --\u003e\n[![Hex.pm Version](http://img.shields.io/hexpm/v/sanity_webhook_plug)](https://hex.pm/packages/sanity_webhook_plug)\n[![Hex docs](http://img.shields.io/badge/hex.pm-docs-blue.svg?style=flat)](https://hexdocs.pm/sanity_webhook_plug)\n[![License](https://img.shields.io/hexpm/l/sanity_webhook_plug)](./LICENSE)\n\n# Sanity Webhook Plug\n\nYou're reading the main branch's readme. Please visit\n[hexdocs](https://hexdocs.pm/sanity_webhook_plug) for the latest published documentation.\n\n\u003c!-- MDOC !--\u003e\n\nSanityWebhookPlug is a Plug that verifies Sanity webhooks for your Elixir Plug\napplication. Designed to work with [Sanity GROQ-powered\nwebhooks](https://www.sanity.io/docs/webhooks)\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:sanity_webhook_plug, \"~\u003e 0.1.3\"}\n  ]\nend\n```\n\n## Usage\n\nUse this plug in your endpoint:\n\n```elixir\n# If using Plug or Phoenix, place before `plug Plug.Parsers`\n# For Phoenix apps, in lib/my_app_web/endpoint.ex:\nplug SanityWebhookPlug,\n  at: \"/webhooks/sanity\",\n  handler: MyAppWeb.SanityWebhookHandler\n```\n\nYou may alternatively configure the secret in config, which will be read during\nruntime:\n\n```elixir\n# in config/runtime.exs\nconfig :sanity_webhook_plug,\n  webhook_secret: System.get_env(\"SANITY_WEBHOOK_SECRET\")\n```\n\nDefine a handler to handle webhooks:\n\n```elixir\ndefmodule MyAppWeb.SanityWebhookHandler do\n  @behaviour SanityWebhookPlug.Handler\n  alias Plug.Conn\n\n  # see below for an example using Phoenix\n\n  @impl SanityWebhookPlug.Handler\n  def handle_event(conn, params) do\n    # Process and return the conn\n\n    conn\n    |\u003e Conn.put_resp_header(\"content-type\", \"application/json\")\n    |\u003e Conn.send_resp(200, Jason.encode!(%{success: \"yay!\"}))\n  end\n\n  @impl SanityWebhookPlug.Handler\n  def handle_error(conn, error) do\n    # Process and return the conn\n\n    conn\n    |\u003e Conn.put_resp_header(\"content-type\", \"application/json\")\n    |\u003e Conn.send_resp(500, Jason.encode!(%{error: \"uh oh!\"}))\n  end\nend\n```\n\n### Options:\n\n- `:at` (required): The request path to match against. eg, `\"/webhooks/sanity\"`\n- `:handler` (required): The controller-like module that responds to\n    `handle_event/2` that is passed the conn and the params, and\n    `handle_error/2` that is passed the conn and the error. The error may be an\n    exception or a string.\n- `:secret`: The Sanity webhook secret. eg: `123abc`. Supplying an MFA tuple will\n    be called at runtime, otherwise it will be compiled. If not set, it will\n    obtain via `Application.get_env(:sanity_webhook_plug, :webhook_secret)`.\n    If supplying an MFA or function reference, it must return `{:ok, my_secret}`\n    or a string.\n- `:json_decoder`: JSON encoding library. When not supplied, it will use choose\n    Phoenix's configured library, `Jason`, or `Poison`. Sanity requires\n    JSON-encoded responses.\n\nOptions forwarded to `Plug.Conn.read_body/2`:\n\n- `:length` - sets the number of bytes to read from the request at a time.\n- `:read_length` - sets the amount of bytes to read at one time from the\n    underlying socket to fill the chunk.\n- `:read_timeout` - sets the timeout for each socket read.\n\nVerifying the signature requires reading the body, but its best to do this\nbefore _interpreting_ the body into JSON or other parsed formats. Plug can\nprotect your system by limiting how much of body to read to prevent exhaustion.\nIdeally, any of these settings you have for `Plug.Parsers` in your endpoint, you\nshould also have for SanityWebhookPlug.\n\nThe body and query params will be merged and given to your handler, which\nmatches Phoenix behavior.\n\n### Example\n\nAn example using Phoenix\n\n```elixir\n## In lib/my_app_web/endpoint.ex\n\n# place before Plug.Parsers\ndefmodule MyAppWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :my_app\n\n  # ...\n\n  plug SanityWebhookPlug,\n    at: \"/webhooks/sanity\",\n    handler: MyAppWeb.SanityWebhookHandler\n\n  # Plug.Parsers down here somewhere\nend\n\n\n## in lib/my_app_web/controllers/sanity_webhook_handler.ex\ndef MyAppWeb.SanityWebhookHandler do\n  use MyAppWeb, :controller\n  require Logger\n  @behaviour SanityWebhookPlug.Handler\n\n  # handle known events\n  def handle_event(conn, %{\"_type\" =\u003e type, \"_id\" =\u003e id}) do\n    # do something\n    json(conn, %{success: \"Did the thing!\"})\n  end\n\n  def handle_event(conn, params) do\n    Logger.warn(\"SanityWebhook: unhandled webhook: #{inspect(params)}\")\n\n    conn\n    |\u003e put_status(500)\n    |\u003e json(%{error: \"unhandled webhook\"})\n  end\n\n  def handle_error(conn, error) do\n    debug = SanityWebhookPlug.get_debug(conn)\n    Logger.error(\"SanityWebhook error: #{inspect(debug)}\")\n\n    conn\n    |\u003e put_status(400)\n    |\u003e json(%{error: inspect(error)})\n  end\nend\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbernheisel%2Fsanity_webhook_plug","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdbernheisel%2Fsanity_webhook_plug","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbernheisel%2Fsanity_webhook_plug/lists"}