Verify webhook signatures on your receiver, and what the module does to stay safe
Verifying the signature on your receiver
Every delivery from a webhook that has a secret configured includes:
X-Webhook-Signature: sha256=<hex-encoded HMAC-SHA256 of the raw request body>The signature is computed over the exact bytes sent on the wire, using the webhook’s secret. It is omitted entirely when no secret is set - never sent empty or fake. On the receiving end:
- Read the raw request body before any JSON parsing or middleware that might re-serialize it.
- Compute the HMAC and prefix it with
sha256=. In PHP:
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $sharedSecret);- Compare it to the received header with a constant-time comparison - PHP’s
hash_equals(), not==- to avoid leaking timing information about the secret.
if (!hash_equals($expected, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '')) { http_response_code(401); exit;}Dedupe on X-Webhook-Delivery-Id after the signature checks out: delivery is at-least-once, so the same delivery ID can legitimately arrive more than once (retries after a timeout your endpoint actually served, for example).
Other tracing headers sent with every delivery: X-Webhook-Event (the event name), X-Webhook-Id (the subscription ID), X-Webhook-Attempt (attempt number), and X-Webhook-Test: 1 on Send Test deliveries only.
Per-webhook authentication
Independent of the signature, each subscription has an authentication type:
- None (default)
- Bearer - sends
Authorization: Bearer <token> - Basic - sends
Authorization: Basic <base64(username:password)>
Bearer tokens, Basic passwords, and HMAC secrets are all encrypted at rest with Magento’s EncryptorInterface. The admin form uses leave-blank-to-keep semantics, so re-saving a webhook never round-trips a plaintext credential back into an HTML form field. Authentication is applied identically on first attempts, cron retries, and Send Test.
SSRF guard
Before a request is sent (and again at save time, for immediate feedback), the destination URL is validated:
- Scheme must be
httporhttps http://URLs are rejected unless the development-only “Allow Insecure URL” flag is on- The host is resolved and rejected if it falls in a private, reserved, loopback, or link-local range, unless the development-only “Allow Private Networks” flag is on
Both flags default to No. Leaving them off in production is what makes the guard meaningful.
The template sandbox
Admin-authored payload templates run in a locked-down Smarty 5 instance: no static class access, no PHP constants, no superglobals, and a strict modifier allowlist. Templates compile from database-stored strings and are never written to disk. A template using anything outside the sandbox fails validation at save time and will never execute at runtime. Template authorship is gated behind the OCMLabs_Webhook::manage ACL resource, so template code is only ever as trusted as your admin role assignments - which is exactly the boundary Magento admins already reason about.