Products · Sandbox
Sandbox + Simulator
Twelve named failure scenarios per provider. Predictable, reproducible test cases — no real wallets, no real money.
12 scenariossk_test_
✓
Getting started
Use API keys starting with
sk_test_. All data is fully isolated per sandbox environment — no real money moves and no provider credentials are required. Sandbox keys are issued automatically when you create a merchant account; find them under Settings → API Keys.How sandbox routing works
Requests authenticated with a sk_test_ key are intercepted by the mock provider adapter before reaching any real payment rail. The mock adapter determines the scenario to simulate by reading one of two inputs:
- MSISDN triggerSet
customer_msisdnto+25300000000XwhereXis a digit from 1 to 12 corresponding to the scenario index in the table below. - Header triggerSet
X-Meras-Scenario: <scenario_name>on the create request. The header takes precedence over the MSISDN trigger when both are present.
Scenario reference
| Index | Scenario name | Providers | Outcome |
|---|---|---|---|
| 1 | success | all | PaymentIntent transitions to succeeded. |
| 2 | insufficient_funds | waafi · dmoney | Fails with code insufficient_funds. |
| 3 | otp_expired | waafi · dmoney | next_action.type collect_otp returned; confirming with any OTP fails with otp_expired. |
| 4 | card_declined | cybersource_cnp | Fails with decline_code generic_decline. |
| 5 | card_3ds_required | cybersource_cnp | Forces the 3DS authentication flow — next_action.type redirect_to_url. |
| 6 | network_timeout | all | Simulates a provider timeout: 30 s delay then an error response. |
| 7 | duplicate_msisdn | waafi | Simulates a locked wallet error. |
| 8 | kyc_blocked | all | Simulates an AML/KYC compliance hold — intent transitions to compliance_review. |
| 9 | account_closed | cac · eab | Account not found error returned by the bank adapter. |
| 10 | velocity_limit | dmoney | Daily velocity limit exceeded error. |
| 11 | partial_capture | cac | Captures 80% of the requested amount; remainder left uncaptured. |
| 12 | sandbox_webhook_fail | all | Webhook delivery is marked failed to allow testing of your retry-handling logic. |
Example — trigger a scenario via header
insufficient_funds via headerbash
curl -X POST https://api.merashub.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: test_order_001" \
-H "Content-Type: application/json" \
-H "X-Meras-Scenario: insufficient_funds" \
-d '{
"amount": 25000,
"currency": "DJF",
"provider": "waafi",
"customer_msisdn": "+25377000099",
"description": "Test — insufficient funds scenario"
}'402 — insufficient_fundsjson
{
"id": "pi_01HZGV2KNRW5X8YZ3AE6BL9QF",
"status": "canceled",
"last_error": {
"code": "insufficient_funds",
"message": "The customer's wallet does not have sufficient funds for this transaction.",
"provider": "waafi"
},
"amount_minor": 25000,
"currency": "DJF",
"created_at": "2026-05-25T14:00:00Z"
}success via MSISDN triggerbash
curl -X POST https://api.merashub.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: test_order_002" \
-H "Content-Type: application/json" \
-d '{
"amount": 10000,
"currency": "DJF",
"provider": "waafi",
"customer_msisdn": "+253000000001",
"description": "Test — success scenario (index 1)"
}'201 — succeeded immediatelyjson
{
"id": "pi_01HZGW3LNSX6Y9ZA4BE7CM0RG",
"status": "succeeded",
"amount_minor": 10000,
"currency": "DJF",
"provider": "waafi",
"succeeded_at": "2026-05-25T14:01:00Z"
}Sandbox utility endpoints
POST
/v1/sandbox/overridesPreload scenario state for a specific payment_intent_id — useful for webhook testing without re-creating the full flow.
POST
/v1/sandbox/webhooks/triggerManually fire any event type to your registered webhook endpoint.
Sandbox override parameters
| Name | Type | Description |
|---|---|---|
payment_intent_idrequired | pi_ | The intent whose outcome you want to preload. |
scenariorequired | string | Scenario name from the table above. The next action on this intent will follow this scenario. |
Webhook trigger parameters
| Name | Type | Description |
|---|---|---|
event_typerequired | string | Any valid event type, e.g. payment_intent.succeeded, payout.failed, invoice.paid. |
related_object_id | string | Optional ID of the object to embed in the event payload. If omitted, a synthetic fixture is generated. |
ℹ
Preloading scenario state
Use
POST /v1/sandbox/overrides to preload scenario state for a specific payment_intent_id. This is useful for testing your webhook delivery handler without needing to re-create the full payment flow — create the intent once, then trigger different outcomes by updating the override.ℹ
Testing webhook delivery
POST /v1/sandbox/webhooks/trigger fires any event type to your registered endpoint immediately. Use this to verify your HMAC signature verification, retry logic, and idempotency handling without having to complete a real payment flow each time.⚠
Sandbox data retention
Sandbox data is purged every 30 days. Do not store sandbox object IDs (pi_, cus_, sub_, etc.) in production systems or use them as references in live flows. Sandbox and live environments are completely isolated at the database level.