Webhooks

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:

HeaderDescription
User-AgentLeadpush-Webhooks/1.0
X-Leadpush-DeliveryDelivery UUID.
X-Leadpush-EventEvent type, such as contact.created.
X-Leadpush-TimestampUnix timestamp used in the signature.
X-Leadpush-SignatureHMAC-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:

  1. Copy the new signing secret from the one-time dialog.
  2. Update the receiver configuration.
  3. Send a test delivery.
  4. 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.

Troubleshooting