Skip to main content

Overview

Once you’ve registered a webhook endpoint, Modulus Labs sends POST requests to your server whenever QR Ph transactions complete. This guide covers how to receive, decrypt, and process these webhooks securely.
1

Receive POST Request

Your server receives a POST request with an encrypted JWE payload
2

Decrypt Payload

Use your secret key to decrypt the JWE-encrypted transaction data
3

Validate Data

Verify the webhook is authentic and hasn’t been processed before
4

Process Transaction

Update order status, send confirmations, trigger fulfillment
5

Respond Quickly

Return 200 OK within 10 seconds to acknowledge receipt

Webhook Request Format

Modulus Labs sends webhooks as POST requests with this structure:

Request Headers

string
required
Always application/json
string
Merchant activation code (for multi-merchant setups). Present if you specified an activation code when creating the webhook.

Request Body

string
required
JWE-encrypted transaction payload. You must decrypt this using your secret key to access the actual transaction data.

Decrypting Webhook Payloads

All webhook payloads are encrypted using JWE (JSON Web Encryption) with:
  • Content Encryption: A256CBC-HS512 (AES-256-CBC with HMAC SHA-512)
  • Key Encryption: A256KW (AES-256 Key Wrap)

Decryption Examples

Required Libraries:
  • Node.js: npm install node-jose
  • Python: pip install jwcrypto
  • PHP: composer require web-token/jwt-framework

Decrypted Payload Structure

After decryption, the webhook payload contains these fields:
string
required
The webhook action type: QRPH_SUCCESS or QRPH_DECLINED
string
required
Unique reference number for this transaction. Matches the referenceNumber from your Create Dynamic QR Ph request.Example: "REF-20240115-ABC123"
integer
required
Transaction amount in the smallest currency unit (centavos for PHP).Example: 100000 (₱1,000.00)
string
required
Three-letter ISO currency code.Example: "PHP"
string
required
ISO 8601 timestamp of when the transaction was processed.Example: "2024-01-15T14:30:00Z"
string
required
Transaction status: SUCCESS, FAILED, PENDING, or REQUIRES_ACTION
string
required
Your merchant identifier.Example: "MERCH12345"
string
Name of the customer who made the payment (if available from the bank).
string
Name of the bank the customer used to pay (if available).

Example Payload: Successful Payment

Example Payload: Declined Payment

Processing Webhooks

Handle Different Webhook Actions

Idempotency and Duplicate Handling

Modulus Labs may send the same webhook multiple times due to retries. Implement idempotency to prevent duplicate processing.

Idempotency Strategies

Always implement idempotency. Without it, you risk charging customers multiple times, sending duplicate emails, or over-fulfilling orders.

Webhook Retry Mechanism

If your endpoint doesn’t respond with 200 OK, Modulus Labs automatically retries:

What Triggers a Retry?

  • Your server returns a status code other than 2xx (e.g., 500, 503, 404)
  • Connection timeout (no response within 10 seconds)
  • Network errors (DNS failure, connection refused, etc.)

Best Practices for Retries

Return 200 OK to acknowledge webhook receipt, even if your business logic fails:
Why? Retrying won’t fix business logic errors (e.g., order not found, inventory depleted). Handle these gracefully without triggering retries.
Use 500/503 status codes only when retrying might help:
Track webhook deliveries to monitor retry patterns:
Use this data to:
  • Identify chronic processing failures
  • Detect unusual retry patterns
  • Debug webhook issues
If your webhook handler calls external services, add retry logic:

Response Requirements

Successful Response

Return 200 OK with a JSON body:
The response body content doesn’t matter - Modulus Labs only checks the status code. However, returning JSON is a good practice for debugging.

Response Timing

Respond within 10 seconds. If your endpoint takes longer, the request times out and triggers a retry.

Asynchronous Processing

For long-running operations, respond immediately and process asynchronously:

Security Best Practices

Verify JWE Encryption

Always decrypt the JWE payload. Never trust unencrypted webhook data - it could be forged.

Validate Payload Structure

Verify the decrypted payload contains expected fields before processing.

Check Reference Number

Confirm the referenceNumber exists in your system before fulfilling orders.

Implement Rate Limiting

Limit webhook requests per IP to prevent abuse and DDoS attacks.

Use HTTPS Only

Reject non-HTTPS webhook URLs in production to prevent man-in-the-middle attacks.

Log Everything

Log all webhook receipts, decryption attempts, and processing results for audit trails.

Error Handling

Graceful Error Recovery

Testing Your Webhook Handler

Local Testing with ngrok

1

Install ngrok

2

Start Your Webhook Server

3

Expose with ngrok

ngrok provides a public HTTPS URL:
4

Register Webhook URL

5

Test with Simulate API

Check your terminal to see the webhook received!
ngrok Inspector: Visit http://localhost:4040 to see all webhook requests in the ngrok web interface.

Monitoring and Alerting

Set up monitoring to detect webhook issues:
Alert if: Success rate drops below 95%
Alert if: Processing takes longer than 5 seconds
Alert if: More than 5 decryption failures in 1 hour (may indicate key mismatch or attack)
Alert if: Webhooks consistently arrive more than 1 minute after transaction

Next Steps

Payload Structure

Complete webhook payload reference

Setup Guide

Register webhook endpoints

Simulate API

Test webhooks in sandbox

Webhook API Reference

Manage webhooks programmatically