Verify Webhook Signatures
Protect webhook receivers by verifying that each request was signed with the destination secret.
Leadpush signs every webhook request with the destination signing secret. Your receiver should verify the signature before processing the payload.
Delivery headers
Each delivery includes these Leadpush headers:
| Header | Description |
|---|---|
User-Agent | Leadpush-Webhooks/1.0 |
X-Leadpush-Delivery | Delivery UUID. |
X-Leadpush-Event | Event type, such as contact.created. |
X-Leadpush-Timestamp | Unix timestamp used in the signature. |
X-Leadpush-Signature | HMAC-SHA256 signature in sha256=<hash> format. |
Signature format
The signature is an HMAC-SHA256 digest using the destination signing secret.
Signed payload:
<timestamp>.<delivery_uuid>.<body>
Header format:
X-Leadpush-Signature: sha256=<hmac_sha256>
If the request has no body, the body portion is an empty string.
Verification steps
Read the headers
Read X-Leadpush-Timestamp, X-Leadpush-Delivery, and X-Leadpush-Signature.
Read the raw request body
Use the exact raw body bytes received by your server. Do not parse and stringify JSON before verifying.
Recreate the signed payload
Join timestamp, delivery UUID, and raw body with periods.
Compute HMAC-SHA256
Use the destination signing secret as the HMAC key.
Compare safely
Compare the expected signature with the received signature using a timing-safe comparison.
Pseudocode
timestamp = request.header("X-Leadpush-Timestamp")
delivery = request.header("X-Leadpush-Delivery")
received = request.header("X-Leadpush-Signature")
body = request.raw_body
payload = timestamp + "." + delivery + "." + body
expected = "sha256=" + hmac_sha256(payload, signing_secret)
if timing_safe_equal(expected, received):
process_webhook()
else:
reject_request()
Timestamp checks
Signature verification confirms that the request was signed with the destination secret. For additional replay protection, reject requests with timestamps outside your accepted window, such as five minutes.
if abs(now_unix_seconds - timestamp) > 300:
reject_request()
Secret rotation
Rotate a destination secret when the receiver changes ownership, a secret may have been exposed, or your security policy requires rotation.
After rotation:
- Copy the new signing secret from the one-time dialog.
- Update the receiver configuration.
- Send a test delivery.
- Confirm the receiver verifies the new signature.
Leadpush does not show a destination signing secret on normal destination views. Store the secret when it is created or rotated.