{"result":{"depiction_url":null,"edges":{"in_module":{"objects":[{"created":"2026-10-22T10:04:00Z","object_id":{"id":3098,"is_a":["text","documentation","reference","module"],"name":"doc_external_6_mod_mailgun","title":"mod_mailgun","uri":"https:\/\/zotonic.com\/id\/3098"},"seq":1}],"predicate":{"id":333,"is_a":["meta","predicate"],"name":"in_module","title":{"_type":"trans","tr":{"en":"In module"}},"uri":"https:\/\/zotonic.com\/id\/in_module"}},"subject":{"objects":[{"created":"2026-10-22T10:04:00Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-10-22T10:04:00Z","object_id":{"id":2564,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_backend_developer","title":"Backend developer","uri":"https:\/\/zotonic.com\/id\/2564"},"seq":2},{"created":"2026-10-22T10:04:00Z","object_id":{"id":2595,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_email_delivery","title":"Email delivery","uri":"https:\/\/zotonic.com\/id\/2595"},"seq":3},{"created":"2026-10-22T10:04:00Z","object_id":{"id":2599,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_api_and_integration","title":"API and integration","uri":"https:\/\/zotonic.com\/id\/2599"},"seq":4},{"created":"2026-10-22T10:04:00Z","object_id":{"id":2626,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_controller","title":"Controller","uri":"https:\/\/zotonic.com\/id\/2626"},"seq":5},{"created":"2026-10-22T10:04:00Z","object_id":{"id":2674,"is_a":["categorization","keyword","keyword_technology"],"name":"zotonic_topic_json","title":"JSON","uri":"https:\/\/zotonic.com\/id\/2674"},"seq":6},{"created":"2026-10-22T10:04:00Z","object_id":{"id":2676,"is_a":["categorization","keyword","keyword_technology"],"name":"zotonic_topic_http","title":"HTTP","uri":"https:\/\/zotonic.com\/id\/2676"},"seq":7}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":3099,"is_a":["text","documentation","reference","controller"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3099"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_external_6_controller_mailgun_webhook","page_url":{"en":"https:\/\/zotonic.com\/docs\/3099\/controller_mailgun_webhook","x-default":"https:\/\/zotonic.com\/docs\/3099\/controller_mailgun_webhook"},"preview_url":null,"resource":{"version":17,"pivot_location_lat":null,"title":"controller_mailgun_webhook","is_authoritative":true,"body":"<p>Receive Mailgun event callbacks at <code>\/mailgun-webhook<\/code> (dispatch name\n<code>mailgun_webhook<\/code>). Accepts <code>POST<\/code> with content type <code>application\/json<\/code>.\nGenerate the site&#39;s callback URL with\n<code>{% url mailgun_webhook absolute_url %}<\/code> and configure it in Mailgun.<\/p>\n<h2>Authentication and payload<\/h2>\n<p>Set the per-site <code>mod_mailgun.webhook_secret<\/code> to the webhook signing key.\nThe JSON body must contain a <code>signature<\/code> object with <code>timestamp<\/code>, <code>token<\/code>\nand <code>signature<\/code> fields. The controller computes HMAC-SHA256 over the timestamp\nconcatenated with the token and compares its lowercase hexadecimal encoding\nwith the supplied signature. A missing signature object or a mismatching\nsignature fails authorization. No Zotonic login or user ACL check is required.\nThe controller does not check timestamp freshness or token reuse.<\/p>\n<p>The <code>event-data<\/code> object contains the event fields. For example, an opened\nevent has <code>event<\/code> set to <code>opened<\/code> and <code>recipient<\/code> set to the email address;\nit must be accompanied by the signature object in the enclosing request.<\/p>\n<h2>Event mapping<\/h2>\n<table class=\"table\"><thead><tr><th>Event<\/th><th>Effect<\/th><\/tr><\/thead><tbody><tr><td><code>delivered<\/code><\/td><td>Calls <code>z_email_server:delivery_report\/4<\/code> with <code>relayed<\/code>.<\/td><\/tr><tr><td><code>opened<\/code>, <code>clicked<\/code><\/td><td>Calls <code>m_email_status:mark_read\/2<\/code> for the recipient.<\/td><\/tr><tr><td><code>failed<\/code><\/td><td>Reports <code>permanent_failure<\/code> for permanent severity, otherwise <code>temporary_failure<\/code>. Unknown or missing severity is logged.<\/td><\/tr><tr><td><code>complained<\/code><\/td><td>Emits <code>email_failed<\/code> with <code>is_final = true<\/code>, reason <code>error<\/code> and status <code>605 Spam complaint<\/code>.<\/td><\/tr><tr><td><code>unsubscribed<\/code><\/td><td>Logs the event and ignores it.<\/td><\/tr><tr><td>Other events<\/td><td>Ignored.<\/td><\/tr><\/tbody><\/table>\n<p>Delivery and failure reports use <code>recipient<\/code>, <code>message.headers.message-id<\/code>\nand <code>delivery-status<\/code>. Failure and complaint events fall back to the event\n<code>id<\/code> when the message ID is absent. Delivery status text is derived from the\ncode, enhanced code, message or description when available.<\/p>\n<p>Requests without an <code>event-data<\/code> map are acknowledged without processing.\nRecognized events require their expected fields; this endpoint does not\nvalidate a complete event schema before dispatching.<\/p>\n<p>The exported <code>handle_event\/2<\/code> function accepts an event-data map and a site\ncontext directly. It bypasses request signature verification and is intended\nfor trusted Erlang callers; its return value depends on the event handler.<\/p>","slug":"controller_mailgun_webhook","doc_module_config":[],"is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"438547e349e499257a4e39ab8737f98e7dcab81aedfd3d54356e5e87f00a50d1","is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"external_module_id":{"id":6,"is_a":[],"name":null,"title":null,"uri":"https:\/\/zotonic.com\/id\/6"},"git_url":"https:\/\/github.com\/zotonic\/zotonic_mod_mailgun.git","category_id":{"id":321,"is_a":["meta","category"],"name":"controller","title":"Controllers","uri":"https:\/\/test.zotonic.com\/id\/321"},"doc_source_path":".\/src\/controllers\/controller_mailgun_webhook.erl","publication_start":"2026-10-22T10:04:00Z","github_url":"https:\/\/github.com\/zotonic\/zotonic_mod_mailgun.git","pivot_location_lng":null,"doc_source_kind":"external","name":"doc_external_6_controller_mailgun_webhook","is_unfindable":false,"is_published":true,"pivot_geocode":null,"external_module_title":"Mailgun","created":"2026-10-22T10:04:00Z","uri":null,"doc_status":"current","is_dependent":false,"is_external_module":true,"publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"ead6c050a61d47e498f9e46208027fe28bdc4d5e","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-10-22T10:04:00Z","title_slug":"controller_mailgun_webhook"},"uri":"https:\/\/zotonic.com\/id\/3099","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/3099"}},"status":"ok"}