Skip to main content

Overview

After you register a callback URL, Modulus sends an HTTPS POST 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 encrypted Token.
  • The Activation-Code header tells you which sub-merchant account the event belongs to (present when you registered the webhook with an activation code).
  • Token is a compact JWE. Decrypt it with your Encryption Key (alg A256KW, enc A256CBC-HS512) - the same key you use elsewhere in the QR Ph API, not your Secret Key.
Decrypt with the Encryption Key, not the Secret Key. The Secret Key authenticates your outbound API calls; the Encryption Key wraps and unwraps JWE payloads. See Encryption & JWE Tokens for the full decryption code in Node.js, Python, and PHP.

Handler pattern

Acknowledge first, process after. Read the Token, 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

Only 200 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

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 A256KW and A256CBC-HS512.
  • Check for whitespace or encoding issues in the key.
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).
Symptoms: the same transaction fulfilled twice.
  • Deduplicate on (transactionId, webhookAction) before processing.
  • Persist processed keys with a unique constraint or ON CONFLICT DO NOTHING to 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