Products · API

Direct API

Server-to-server PaymentIntents with idempotency guarantees, webhooks, retries, and the full Meras resource model — no redirect, no hosted page.

Server-to-serversk_*

Direct API vs Hosted Checkout

CapabilityDirect APIHosted Checkout
UI controlFull — your UI, your UXMerasPay-hosted page
Credential handlingYou collect and forward tokensHandled by MerasPay
next_action logicYou implement per railHandled automatically
PCI scopeSAQ-D (with card) or SAQ-A-EPSAQ-A — minimal scope
Integration effortHigher — full control requiredMinimal — redirect only
Custom flows (e.g., BNPL)SupportedNot 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 headerbash
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 headerbash
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.

Version headerbash
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

EnvironmentLimitScope
Sandbox1,000 req / minPer merchant
Live5,000 req / minPer 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.

typeMeaningTypical HTTP status
card_errorThe card or mobile-money account declined.402
invalid_request_errorMissing or malformed request parameter.400
authentication_errorInvalid or missing API key.401
rate_limit_errorToo many requests in the current window.429
api_errorMerasPay-side error — safe to retry after delay.500
Error response envelopejson
{
  "error": {
    "type": "invalid_request_error",
    "message": "amount must be a positive integer",
    "param": "amount"
  }
}

Full idempotent create + confirm cycle

  1. 1

    Create the intent

    POST to /v1/payment_intents with your idempotency key. Store the returned intent ID.
  2. 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. 3

    Confirm

    POST to /v1/payment_intents/{id}/confirm with the OTP (or nothing for redirect-based rails).
  4. 4

    Verify via webhook

    Listen for payment_intent.succeeded. This is the authoritative fulfillment signal.
Create + confirm (Waafi)bash
# 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

See the Payment Intents API reference for the complete parameter list, response schema, status state machine, and all five endpoints.