Skip to main content
When you attach a webhook to a transaction, chmod calls your endpoint as the transaction changes status — most importantly when the decision is ready.
The URL is set per transaction and must be HTTPS. Omit webhook and no notification is sent — you would then have to poll.

The request

chmod sends a POST with two headers that let you prove the call came from us: The body identifies the transaction and its new status.
Treat the webhook as a notification, not as data. It tells you a transaction has moved; it is not the source of truth for the decision. Fetch the transaction with GET /kyc/transaction/{id} to read the actual result, and act on that.This keeps you safe even if someone replays or forges a call: the verdict you act on always comes from an authenticated request you made yourself.

Verifying the signature

chmod signs with a private key and publishes the matching public key as a JWKS document:
To verify a call:
1

Read both headers

Reject the request outright if X-Timestamp or X-Signature is missing.
2

Rebuild the signed string

Concatenate the timestamp, a colon, and the transaction id from the body: {timestamp}:{transactionId}. No spaces, no trailing newline.
3

Verify against the public key

Fetch the JWKS (cache it — do not fetch per request) and verify X-Signature over that string. The alg in the JWKS entry tells you which algorithm to use.
4

Check the timestamp is recent

Reject anything older than a few minutes. A valid signature stays valid forever, so without this check a captured request can be replayed indefinitely.
A webhook endpoint that does not verify the signature is an open endpoint: anyone who learns the URL can tell you a transaction was approved. Verify before you act.

Responding

Return 2xx as soon as you have accepted the call. Do the real work — fetching the transaction, updating your records, notifying the user — out of band. A slow handler is a fragile handler: if your endpoint blocks on a database write and times out, the delivery counts as failed even though you received it.

Delivery and idempotency

Assume at-least-once delivery. A retry after a network hiccup means the same status can reach you twice, so make your handler idempotent — key on transaction_id and the status, and make a repeat a no-op

Not receiving calls?

When a webhook is genuinely lost, polling is the fallback — which is why a reconciliation job over transactions that never reached a terminal status is worth having in production.