Skip to main content

Overview

Setting up webhooks involves two main steps: implementing a webhook endpoint on your server and registering it with Modulus Labs. This guide walks you through the complete setup process.
1

Implement Webhook Endpoint

Create a POST endpoint on your server that can receive and process webhook notifications
2

Register with Modulus Labs

Use the Create Webhook API to register your endpoint URL
3

Test Your Integration

Use the Simulate API to verify your webhook handler works correctly
4

Go Live

Enable your webhook and start receiving real transaction notifications

Prerequisites

Before registering a webhook, ensure you have:

Publicly Accessible Endpoint

Your webhook URL must be accessible from the internet (not localhost)

HTTPS Support

Webhook URLs must use HTTPS for security (HTTP not supported in production)

Encryption Key

For decrypting the webhook JWE. Your Secret Key is separate - it authenticates your management-API calls.

Fast Response Time

Endpoint must respond promptly to avoid timeouts
Development URLs: During sandbox testing, you can use services like ngrok, localtunnel, or similar tools to expose localhost endpoints. Never use these in production.

Step 1: Implement Your Webhook Endpoint

Your server needs one POST endpoint that accepts Content-Type: application/json, decrypts the delivery, and returns 200 or 201 quickly. The delivery body is { "Token": "<JWE>" }. Decrypt the Token with your Encryption Key (not the Secret Key), deduplicate on (transactionId, webhookAction), acknowledge, then process off the request path. Receiving Webhooks has the complete, copy-ready handler, and Webhook Payload documents every field you get back. Build your endpoint from those, then come back here to register it.
Decrypt with the Encryption Key, correlate to your order on merchantReferenceNumber, and return 200 or 201 only after you have durably stored the event - failed deliveries are retried up to 3 more times, about 15 minutes apart, then stop (4 attempts total).

Step 2: Register Your Webhook

The webhook management API is encrypted end to end. You send an encrypted envelope and get one back:
Build the Token by encrypting your JSON body as a JWE with your Encryption Key (alg A256KW, enc A256CBC-HS512) - the same key and code you use in Encryption & JWE Tokens. Decrypt the response.Token the same way. Authenticate the call itself with HTTP Basic Auth using your Secret Key. A webhook registers one action. To receive both success and declined events, register twice. Body you encrypt (create):
Send it:
Actual response (on the wire):
Decrypted response.Token:
Save the id to update or delete the webhook later. See Create Webhook for the full field list.
webhookAction is singular - one webhook, one action (QRPH_SUCCESS or QRPH_DECLINED). There is no actions array and no status field on create; new webhooks are enabled.

Step 3: Test Your Webhook

Use the Simulate API to send a real encrypted webhook to your endpoint without a live transaction. It takes the same encrypted envelope. The body you encrypt is:
useCase is one of SUCCESS, MISSING_DESTINATION_ACCOUNT, MISSING_PARTNER_REVENUE_ACCOUNT, or UNSUPPORTED_TRANSFER_TYPE. The three failure cases drive a QRPH_DECLINED delivery.
The response is the same encrypted envelope:
See Simulate Webhook for the full contract.

Managing Webhooks

All management calls use the same encrypted envelope and Basic Auth.
  • Update - encrypt { "callbackUrl": "...", "webhookStatus": "ENABLED" } (both required; webhookStatus is ENABLED or DISABLED). Update cannot change the action - delete and re-create to change it. Disable instead of deleting during maintenance.
  • List - GET /v1/webhooks returns your webhooks in an encrypted response.Token.
  • Delete - DELETE /v1/webhooks/{id} permanently removes one.
See the Webhooks API reference for each operation’s exact fields.

Multi-Merchant Support

Each delivery carries an Activation-Code header identifying which sub-merchant account the event belongs to. Route on it:

Best Practices

Use HTTPS

Register only HTTPS callback URLs. HTTP is not supported in production.

Separate URLs per environment

Use different callback URLs for sandbox and production so test and live data never mix.

Deduplicate every delivery

Retries resend the same body. Key on (transactionId, webhookAction) before acting.

Acknowledge with 200 or 201

Only 200 and 201 stop retries. Return one after you durably store the event; log business-logic failures and retry them yourself.

Troubleshooting

Possible causes:
  • Callback URL is not publicly accessible
  • Firewall blocking incoming requests
  • Webhook status is DISABLED
  • TLS certificate issues
Solutions:
  • Test URL accessibility from an external network
  • Verify status is ENABLED via GET /v1/webhooks
  • Check server logs for incoming requests
  • Use the Simulate API to send a test webhook
Possible causes:
  • Decrypting with the Secret Key instead of the Encryption Key
  • JWE library not configured correctly
  • Reading the wrong body field (the JWE is in Token)
Solutions:
  • Verify you are using your Encryption Key, the same key used elsewhere in the QR Ph API
  • Log the raw Token for inspection
  • Test decryption with the Simulate API first
  • Check JWE library documentation for your language
Possible causes:
  • Retries (expected when you did not return 200/201)
  • Slow endpoint timing out before acknowledging
Solutions:
  • Deduplicate on (transactionId, webhookAction) before processing
  • Persist processed keys with a unique constraint to survive races
  • Acknowledge fast, then process asynchronously

Security Checklist

1

Transport

  • Callback URL uses HTTPS with a valid certificate
2

Authenticity

  • Every delivery decrypts with your Encryption Key (a body you cannot decrypt is not from Modulus)
  • Correlate to your order on merchantReferenceNumber
3

Idempotency

  • Deduplicate on (transactionId, webhookAction)
  • Reject or ignore an event you have already processed
4

Endpoint hygiene

  • Errors do not leak system internals
  • Decryption and processing failures are logged and alerted

Next Steps

Receiving Webhooks

Decrypt, acknowledge, and process

Webhook Payload

Every field in the decrypted payload

Create Webhook API

Full create/update/delete reference

Simulate API

Test with SUCCESS and the failure cases