Direct API
Server-to-server PaymentIntents with idempotency guarantees, webhooks, retries, and the full Meras resource model — no redirect, no hosted page.
Direct API vs Hosted Checkout
| Capability | Direct API | Hosted Checkout |
|---|---|---|
| UI control | Full — your UI, your UX | MerasPay-hosted page |
| Credential handling | You collect and forward tokens | Handled by MerasPay |
| next_action logic | You implement per rail | Handled automatically |
| PCI scope | SAQ-D (with card) or SAQ-A-EP | SAQ-A — minimal scope |
| Integration effort | Higher — full control required | Minimal — redirect only |
| Custom flows (e.g., BNPL) | Supported | Not supported |
Authentication
All API requests authenticate with a secret key in the Authorization header. Sandbox keys start with sk_test_; production keys with sk_live_. Never expose secret keys in client-side code or version control.
Authorization: Bearer sk_live_...
Publishable keys (pk_*) are safe for browser use and can create PaymentIntents but require a client_secret to confirm. Use secret keys only on your server.
Idempotency
Every mutating request (POST, PATCH) should carry an Idempotency-Key header. The same key within 24 hours returns the original response and never creates a duplicate resource. Use your internal order ID or transaction reference as the key — it must be unique per merchant per operation type.
Idempotency-Key: order_tx_20260525_8842
If a request times out or you receive a network error, retry with the same key. MerasPay will deduplicate the attempt and return the original intent — no double-charge risk.
API versioning
Pin a version by sending the MerasPay-Version header on every request. You can also set a default version per API key in the dashboard — requests without the header use that default. Breaking changes are never applied to pinned versions.
MerasPay-Version: 2026-01-01
New non-breaking additions (new fields, new enum values) are introduced without a version bump. When a breaking version is released, you receive 12 months notice before the old version is retired.
Rate limits
| Environment | Limit | Scope |
|---|---|---|
| Sandbox | 1,000 req / min | Per merchant |
| Live | 5,000 req / min | Per merchant |
When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating seconds until the next window opens. Implement exponential back-off with jitter in your retry logic.
Error format
All error responses use a consistent envelope. Inspect error.type to branch your error handling; error.param is set when the error is attributable to a specific request field.
| type | Meaning | Typical HTTP status |
|---|---|---|
card_error | The card or mobile-money account declined. | 402 |
invalid_request_error | Missing or malformed request parameter. | 400 |
authentication_error | Invalid or missing API key. | 401 |
rate_limit_error | Too many requests in the current window. | 429 |
api_error | MerasPay-side error — safe to retry after delay. | 500 |
{
"error": {
"type": "invalid_request_error",
"message": "amount must be a positive integer",
"param": "amount"
}
}Full idempotent create + confirm cycle
- 1
Create the intent
POST to /v1/payment_intents with your idempotency key. Store the returned intent ID. - 2
Handle next_action
If status is requires_action, branch on next_action.type — collect OTP, redirect to bank portal, or display USSD string. - 3
Confirm
POST to /v1/payment_intents/{id}/confirm with the OTP (or nothing for redirect-based rails). - 4
Verify via webhook
Listen for payment_intent.succeeded. This is the authoritative fulfillment signal.
# Step 1 — Create
curl -X POST https://api.merashub.com/v1/payment_intents \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: order_8842" \
-H "MerasPay-Version: 2026-01-01" \
-H "Content-Type: application/json" \
-d '{
"amount": 20000,
"currency": "DJF",
"provider": "waafi",
"customer_msisdn": "+25377xxxxxxx",
"description": "Order #8842"
}'
# Step 2 — Confirm with OTP
curl -X POST https://api.merashub.com/v1/payment_intents/pi_01JXYZ.../confirm \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: order_8842_confirm" \
-H "MerasPay-Version: 2026-01-01" \
-H "Content-Type: application/json" \
-d '{ "otp": "512940" }'Full endpoint reference