Verifying webhook signatures
Propper Click signs every outbound webhook so your service can verify the request came from us and was not tampered with in transit. Verification is mandatory for any production integration that acts on click.* events.
Headers
Each webhook POST includes:
| Header | Example | Purpose |
|---|---|---|
X-Propper-Signature | t=1716900000,v1=abc123... | Timestamp + HMAC-SHA256 |
X-Propper-Timestamp | 1716900000 | Same Unix epoch as t= above |
X-Propper-Event-Id | evt_... | Unique event ID (idempotency on your side) |
X-Propper-Event-Type | click.acceptance.completed | What happened |
X-Propper-Delivery-Id | whd_... | Per-delivery ID, useful in support tickets |
Signature format
The X-Propper-Signature header is Stripe-compatible:
t=<unix_seconds>,v1=<hmac_hex>[,v1=<hmac_hex>...]
Multiple v1= entries appear during a key rotation window — verify against each of your active secrets and accept if any matches.
The HMAC is computed as:
HMAC_SHA256(secret, "<timestamp>.<raw_body>")
The signature is computed over the exact bytes the sender wrote. If your framework JSON-parses and re-serializes before you verify, the signature will not match. Read raw bytes first, verify, then parse.
Replay protection
t= is the Unix-seconds timestamp at signing time. Reject any webhook where |now - t| > 300 to defend against replay. The SDK verifiers do this for you with a default 300-second window (configurable).