Skip to main content

Verification

Every webhook request carries a set of identifying headers. Check them before trusting a payload.

Headers sent with every delivery

v1: shared-secret verification

The current version uses a shared-secret model: your receiving endpoint should compare the X-Webhook-Secret header against the secret shown on the endpoint’s detail page in the CRM, and reject the request if it doesn’t match.
Use a constant-time comparison (hash_equals in PHP, or an equivalent) rather than ==/=== where your language’s standard library provides one, to avoid timing attacks.

Keep the secret private

Treat the signing secret like a password. Anyone who has it can send requests to your endpoint that your code will accept as genuine. If a secret leaks, delete the endpoint and recreate it — a new secret is generated automatically. A shared secret sent on every request has two limitations: it travels over the wire on every call (so it must never be logged or sent over plain HTTP), and it doesn’t protect the payload itself — nothing stops a captured request from being replayed later. A future version of this module is expected to add HMAC-SHA256 request signing, the same approach used by Stripe and GitHub:
  • The endpoint’s secret is used as an HMAC key, never sent on the wire itself.
  • The signature is computed over timestamp + "." + raw_body, sent in a header such as X-Webhook-Signature: t=1730000000,v1=<hex-hmac>.
  • Your code recomputes the HMAC locally and compares it, and rejects requests whose timestamp is too old (protecting against replay).
If you are building a new integration today, structure your verification code so the comparison is isolated in one function — swapping a shared-secret check for an HMAC check later will then be a small, contained change.