Overview
After you register a callback URL, Modulus sends an HTTPSPOST to it whenever a QR Ph transaction reaches a terminal state. This guide covers the runtime handling: decrypt the delivery, acknowledge it fast, then process it once.
For the exact fields inside a decrypted webhook, see Webhook Payload.
The delivery
Every delivery has the same shape: a JSON body with a single encryptedToken.
- The
Activation-Codeheader tells you which sub-merchant account the event belongs to (present when you registered the webhook with an activation code). Tokenis a compact JWE. Decrypt it with your Encryption Key (algA256KW,encA256CBC-HS512) - the same key you use elsewhere in the QR Ph API, not your Secret Key.
Handler pattern
Acknowledge first, process after. Read theToken, decrypt it, deduplicate, persist, return 200 immediately, then do the slow work (fulfillment, email, ledger writes) outside the request.
Correlate the payment to your order on
merchantReferenceNumber - the value you supplied when creating the QR. The payload does not echo a referenceNumber. See Webhook Payload.Idempotency
Deliveries are not uniquely keyed, and retries resend the same encrypted body, so you will see the same event more than once. Deduplicate on the pair(transactionId, webhookAction) before you act, and make processing safe to run twice: use INSERT ... ON CONFLICT DO NOTHING on that key, or check-then-set in a transaction.
Retries
Only200 and 201 acknowledge a delivery. Any other status (or a timeout) is a failure, and Modulus makes up to 4 total delivery attempts (the initial delivery plus up to 3 retries), about 15 minutes apart. After that the delivery is dropped.
Return 200 (or 201) only once you have safely recorded the event. If you acknowledge before persisting and then crash, that event is gone - the retry window has closed. If processing might fail, acknowledge after you have durably stored the decrypted payload, and retry your own downstream work internally.
Responding
Keep the request fast. Decrypt, dedupe, persist, respond - then do fulfillment asynchronously.
Security
A webhook is genuine only if you can decrypt it with your Encryption Key. There is no signature header - the JWE’s authenticated encryption is the integrity and authenticity check. So a body you cannot decrypt is not one Modulus sent: decrypt first, deduplicate, then process, and never act on a payload you could not decrypt.Use HTTPS only
Register only HTTPS callback URLs. HTTP is not supported in production.
Keep the Encryption Key secret
Never place it in client-side code, logs, or version control.
Common issues
Decryption fails
Decryption fails
Symptoms: cannot decrypt the
Token, or “invalid token” errors.- Confirm you are using the Encryption Key, not the Secret Key.
- Check the key matches the one Modulus provided for this environment (sandbox vs production keys differ).
- Confirm the JWE algorithms
A256KWandA256CBC-HS512. - Check for whitespace or encoding issues in the key.
Endpoint not reachable
Endpoint not reachable
Symptoms: no deliveries arriving.
- Verify the endpoint is publicly reachable over HTTPS.
- Check firewall rules allow inbound HTTPS and the TLS certificate is valid (not self-signed).
Duplicate processing
Duplicate processing
Symptoms: the same transaction fulfilled twice.
- Deduplicate on
(transactionId, webhookAction)before processing. - Persist processed keys with a unique constraint or
ON CONFLICT DO NOTHINGto survive races.
IP allowlisting
For defense in depth, restrict inbound traffic to Modulus webhook IPs.Contact support@moduluslabs.io for the webhook IP ranges for your environment (sandbox and production differ).
Next steps
Webhook Payload
Every field in the decrypted payload
Encryption & JWE
Full decryption code for the Token
Register a webhook
Create and manage your callback URLs
Overview
How webhooks work and what they deliver