Skip to main content
POST
/
v1
/
webhooks
/
qrph
/
simulate
Simulate Webhook
curl --request POST \
  --url https://webhooks.sbx.moduluslabs.io/v1/webhooks/qrph/simulate \
  --header 'Authorization: Basic <encoded-value>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "qrBody": "00020101021228820011ph.ppmi.p2m0111CUOBPHM2XXX03258eff02de-e172-4b0d-bc5b-3041288500100000705033015204601653036085406100.005802PH5912MAIN ACCOUNT6006MANILA630412C0",
  "useCase": "SUCCESS"
}
'
{
"id": "a78efd32-de3b-4854-b599-11ae9f98f97e"
}

Overview

The Simulate Webhook endpoint allows you to test your webhook integration without creating real QR codes or processing actual transactions. It sends encrypted webhook notifications to your registered endpoint, just like real transaction webhooks.
Sandbox Only: This endpoint is only available in the sandbox environment for testing purposes. It’s not available in production.
The simulated webhook payloads sent to your endpoint are JWE-encrypted, identical to real transaction webhooks. See the Encryption Guide to understand how to decrypt incoming notifications.

Endpoint

POST https://webhooks.sbx.moduluslabs.io/v1/webhooks/qrph/simulate

Authentication

This endpoint requires HTTP Basic Authentication using your Secret Key.
Authorization: Basic {base64(secret_key:)}

Request

Headers

HeaderValueRequired
AuthorizationBasic {base64(secret_key:)}Yes
Content-Typeapplication/jsonYes

Body Parameters

qrBody
string
required
The raw QRPH payload used to simulate a transaction.Example: "00020101021228820011ph.ppmi.p2m0111CUOBPHM2XXX03258eff02de-e172-4b0d-bc5b-3041288500100000705033015204601653036085406100.005802PH5912MAIN ACCOUNT6006MANILA630412C0"
useCase
string
required
The type of transaction event to simulate.Possible values:
  • SUCCESS - Simulates a successful payment
  • DECLINED - Simulates a declined payment
Example: "SUCCESS"

Request Examples

{
  "qrBody":
    "00020101021228820011ph.ppmi.p2m0111CUOBPHM2XXX03258eff02de-e172-4b0d-bc5b-3041288500100000705033015204601653036085406100.005802PH5912MAIN ACCOUNT6006MANILA630412C0",
  "useCase": "SUCCESS"
}

Response

Success Response

Status Code: 200 OK Returns confirmation that the simulated webhook was sent to your registered endpoint.
id
string
The unique identifier of the original transaction (i.e., the id returned from the Create Dynamic QR Ph reponse).Example: "a78efd32-de3b-4854-b599-11ae9f98f97e"

Response Example

{
"id": "a78efd32-de3b-4854-b599-11ae9f98f97e"
}
{
"id": "a78efd32-de3b-4854-b599-11ae9f98f97e"
}

Error Responses

Status Code: 400Causes:
  • Missing useCase field
  • Invalid useCase value
Response Example:
{
  "code": "20000002",
  "error": "Invalid useCase: must be SUCCESS or DECLINED",
  "referenceNumber": "abc123-def456-ghi789"
}
Solution:
  • Ensure useCase is either SUCCESS or DECLINED
  • Check spelling and capitalization
Status Code: 401Cause: Invalid or missing authentication credentialsSolution:
  • Verify your secret key is correct
  • Ensure Authorization header format: Basic {base64(secret_key:)}
Status Code: 404Cause: No enabled webhooks configuredResponse Example:
{
  "code": "20000005",
  "error": "No enabled webhooks found",
  "referenceNumber": "abc123-def456-ghi789"
}
Solution:
  • Create at least one webhook using Create Webhook API
  • Ensure webhook status is ENABLED
  • Verify webhook actions include the simulated event type
Status Code: 500Cause: Unexpected server errorSolution:
  • Retry the request
  • If the issue persists, contact Modulus Labs support

Code Examples

curl -X POST https://webhooks.sbx.moduluslabs.io/v1/webhooks/qrph/simulate \
  -u sk_your_secret_key: \
  -H "Content-Type: application/json" \
  -d '{
  "qrBody":
    "00020101021228820011ph.ppmi.p2m0111CUOBPHM2XXX03258eff02de-e172-4b0d-bc5b-3041288500100000705033015204601653036085406100.005802PH5912MAIN ACCOUNT6006MANILA630412C0",
  "useCase": "SUCCESS"
}'

Testing Workflow

1

Register Webhook

Create a webhook endpoint pointing to your test server:
const webhook = await createWebhook(
  'https://your-test-server.com/webhooks/modulus',
  ['QRPH_SUCCESS', 'QRPH_DECLINED'],
  'ENABLED'
);
2

Simulate Success Event

Send a successful payment webhook:
await simulateWebhook('SUCCESS');
Check your webhook endpoint receives and processes the notification correctly.
3

Simulate Declined Event

Send a declined payment webhook:
await simulateWebhook('DECLINED');
Verify your endpoint handles payment failures gracefully.
4

Test Error Scenarios

Test your webhook error handling:
  • Return 5xx status to trigger retries
  • Delay response beyond 10 seconds to test timeouts
  • Verify idempotency by sending duplicate webhooks
5

Verify Decryption

Confirm your endpoint can decrypt JWE payloads successfully using your secret key.

Use Cases

Test your webhook handler during development:
console.log('Testing webhook integration...');

// Test success case
await simulateWebhook('SUCCESS');
await wait(2000);

// Test decline case
await simulateWebhook('DECLINED');
await wait(2000);

console.log('Webhook integration test complete');
Include webhook simulation in your test suite:
describe('Webhook Integration', () => {
  it('should handle successful payments', async () => {
    const result = await simulateWebhook('SUCCESS');
    expect(result.webhooksSent).toBeGreaterThan(0);

    // Wait for webhook to be processed
    await wait(3000);

    // Verify order was marked as paid
    const order = await db.orders.findOne({ status: 'paid' });
    expect(order).toBeDefined();
  });

  it('should handle declined payments', async () => {
    const result = await simulateWebhook('DECLINED');
    expect(result.webhooksSent).toBeGreaterThan(0);

    // Verify order was marked as failed
    await wait(3000);
    const order = await db.orders.findOne({ status: 'payment_failed' });
    expect(order).toBeDefined();
  });
});
Test your JWE decryption logic:
// Send simulated webhook
await simulateWebhook('SUCCESS');

// Check server logs to verify decryption succeeded
console.log('Check your webhook endpoint logs for:');
console.log('- JWE decryption success message');
console.log('- Decrypted payload structure');
console.log('- Webhook processing result');
Verify your webhook handler manages errors gracefully:
// Test idempotency by sending same webhook twice
await simulateWebhook('SUCCESS');
await wait(1000);
await simulateWebhook('SUCCESS');

// Verify duplicate webhook was ignored
// (should only process once)
Demonstrate webhook functionality without real transactions:
console.log('Starting live webhook demo...');

// Simulate successful payment
console.log('Simulating successful payment...');
await simulateWebhook('SUCCESS');

await wait(2000);

// Simulate declined payment
console.log('Simulating declined payment...');
await simulateWebhook('DECLINED');

console.log('Demo complete!');

Testing Checklist

1

Setup

  • Webhook endpoint is accessible
  • Webhook is registered and enabled
  • Webhook actions include QRPH_SUCCESS and QRPH_DECLINED
2

Test Successful Payment

  • Simulate SUCCESS webhook
  • Webhook received at endpoint
  • JWE payload decrypted successfully
  • Order marked as paid
  • Confirmation email sent
3

Test Declined Payment

  • Simulate DECLINED webhook
  • Webhook received at endpoint
  • Decline reason parsed correctly
  • Order marked as failed
  • Customer notified
4

Test Error Handling

  • Duplicate webhooks ignored (idempotency)
  • Invalid webhooks rejected
  • Timeout scenarios handled
  • Retry mechanism tested
5

Verify Logging

  • All webhooks logged
  • Processing results recorded
  • Errors captured

Best Practices

Test Both Scenarios

Always test both SUCCESS and DECLINED webhooks:
async function runFullWebhookTest() {
  // Test success
  await simulateWebhook('SUCCESS');
  await verifySuccessHandling();

  // Test declined
  await simulateWebhook('DECLINED');
  await verifyDeclineHandling();

  console.log('All webhook tests passed');
}

Use in CI/CD

Automate webhook testing in your deployment pipeline:
# .github/workflows/test.yml
- name: Test Webhook Integration
  run: |
    npm run start:test-server &
    sleep 5
    npm run test:webhooks

Monitor Test Results

Track webhook simulation outcomes:
const testResults = {
  successTests: 0,
  declinedTests: 0,
  failures: 0
};

try {
  await simulateWebhook('SUCCESS');
  testResults.successTests++;
} catch (error) {
  testResults.failures++;
}

console.log('Test results:', testResults);

Test with Delays

Allow time for webhook processing between tests:
await simulateWebhook('SUCCESS');
await wait(2000); // Wait 2 seconds

await simulateWebhook('DECLINED');
await wait(2000);

// Now verify results

Differences from Real Webhooks

AspectSimulate APIReal Webhooks
TriggerManual API callActual QR Ph transaction
Reference NumberSIM-REF-* prefixReal reference from QR creation
Transaction DataTest dataReal customer payment data
TimingImmediateDepends on payment processing
RetriesSame as realUp to 4 attempts (0, 15, 30, 45 min)
EncryptionJWE encryptedJWE encrypted
AvailabilitySandbox onlySandbox and production
Simulated webhooks behave identically to real webhooks except for the data content. Your webhook handler should process them the same way.

Troubleshooting

Symptom: Receive 404 error when calling Simulate APIPossible Causes:
  • No webhooks registered
  • All webhooks are disabled
  • Webhook actions don’t include the simulated event
Solutions:
// Check existing webhooks
const webhooks = await getWebhooks();
console.log('Webhooks:', webhooks);

// Ensure at least one is enabled
const enabled = webhooks.filter(w => w.status === 'ENABLED');

if (enabled.length === 0) {
  // Create or enable a webhook
  await createWebhook(
    'https://your-server.com/webhooks',
    ['QRPH_SUCCESS', 'QRPH_DECLINED'],
    'ENABLED'
  );
}
Symptom: Simulate API returns success but webhook not receivedPossible Causes:
  • Webhook URL not accessible
  • Firewall blocking requests
  • Endpoint not listening
Solutions:
  • Test webhook URL accessibility externally
  • Check server logs for incoming requests
  • Verify endpoint is running and listening
  • Test with ngrok or similar during development
Symptom: Webhook received but cannot decrypt payloadPossible Causes:
  • Wrong secret key
  • JWE library issue
  • Incorrect algorithm configuration
Solutions:
// Verify secret key
console.log('Secret key length:', SECRET_KEY.length);
console.log('Secret key starts with:', SECRET_KEY.substring(0, 3));

// Test decryption
try {
  const payload = await decryptWebhook(webhookData, SECRET_KEY);
  console.log('Decryption successful:', payload);
} catch (error) {
  console.error('Decryption failed:', error);
}
Symptom: Receive multiple webhook deliveries for one simulationCause: Multiple enabled webhooks registeredExpected Behavior: The Simulate API sends notifications to all enabled webhooks that are configured for the simulated event.Verification:
const result = await simulateWebhook('SUCCESS');
console.log(`Webhooks sent: ${result.webhooksSent}`);
// This tells you how many webhooks received the notification

Next Steps

Authorizations

Authorization
string
header
required

HTTP Basic Authentication using your Secret Key as the username and an empty password

Body

application/json
qrBody
string
required

The raw QRPH payload returned after the simulation

useCase
enum<string>
required

The type of transaction event to simulate

Available options:
SUCCESS,
DECLINED

Response

Webhook simulation sent successfully

id
string

The unique identifier of the original transaction (i.e., the id returned from the Create Dynamic QR Ph reponse).