Merchant API v1

Accept payments safely from sandbox to production.

Use the workspace to complete verification, generate environment-specific credentials, configure webhooks, and monitor every payment and settlement.

Server-side keys

Use secret keys only from your backend. Never ship them in browser, mobile, source control, or logs.

Idempotent money movement

Send a stable Idempotency-Key for every create request so retries cannot create duplicate intents.

Signed events

Verify the timestamped HMAC signature against the exact raw request body before processing.

1. Environments and credentials

Sandbox and live data are isolated. Start with the sk_test_ secret and the sandbox base URL shown in your API Keys page. After approval, issue live credentials and switch to the live base URL. A secret is revealed only when issued or regenerated; store it in a secret manager.

Sandbox

Safe fake-money testing. Pending merchants can use it before approval.

Live

Real customer money. Available only after approval and live credential issuance.

2. Authentication

Every private request uses a merchant secret key in the Authorization header. Do not use a public key for server-to-server API calls.

Authorization: Bearer sk_test_REPLACE_ME
Content-Type: application/json
Accept: application/json

3. Create and complete a payment

Create an intent on your server, redirect the customer to checkout_url, and use webhooks as the source of truth for final status. A browser redirect is not proof of payment.

curl -X POST https://api.example.com/api/merchant/v1/payment-intents \
  -H "Authorization: Bearer sk_test_REPLACE_ME" \
  -H "Idempotency-Key: order-1042-attempt-1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "50000.00",
    "currency": "GNF",
    "description": "Order 1042",
    "external_reference": "order_1042",
    "return_url": "https://shop.example.com/payments/complete"
  }'

Successful response

{
  "id": 1042,
  "status": "pending_payment",
  "mode": "sandbox",
  "amount": "50000.00",
  "currency": "GNF",
  "checkout_url": "https://your-app/checkout/1042",
  "expires_at": "2026-09-18T22:00:00Z"
}

4. Idempotency and retries

Generate one stable key per business operation, such as order-1042-payment. Reuse the same key when retrying after a timeout. Never generate a new key for an uncertain retry, or you may create another payment intent.

Validate that your request body matches the original operation before reusing a key. Treat 4xx validation errors as actionable; retry 429 and transient 5xx responses with exponential backoff.

5. Webhooks

Configure an HTTPS endpoint in the workspace. Handle duplicate deliveries, acknowledge quickly with a 2xx response, and process the event asynchronously. Verify both the timestamp and signature before parsing business data.

Verification outline

signed = f"{timestamp}.{raw_body}".encode()
expected = hmac_sha256(webhook_secret, signed)
if not constant_time_compare(expected, signature):
    reject_request()
if abs(now - int(timestamp)) > 300:
    reject_request()

Useful events include payment_intent.succeeded, payment_intent.failed, payment_intent.cancelled, and payment_intent.expired. Store the delivery ID to make processing idempotent.

6. API reference

MethodPathPurpose
POST/payment-intentsCreate a checkout payment intent
GET/payment-intents/:idRetrieve a payment intent
POST/payment-intents/:id/cancelCancel an unpaid intent
GET/transactionsList merchant transactions
GET/transactions/:idRetrieve a transaction
GET/settlementsList settlement batches
GET/disputesList merchant disputes
POST/disputesOpen a dispute or refund request
POST/webhooks/:delivery_id/retryRetry a failed webhook delivery

7. Errors and production behavior

401 / authentication_failed

Missing, malformed, expired, or invalid secret. Check the environment and Authorization header.

403 / merchant_not_active

The account or live environment is not approved. Continue in sandbox or contact support.

429 / rate_limit_exceeded

Back off and retry with jitter. Do not run an aggressive loop.

expired / invalid_status

The intent cannot be completed in its current state. Fetch it before taking action.

Use decimal strings for amounts, uppercase ISO currency codes, HTTPS in production, structured request IDs in your logs, and never log secrets, full webhook payloads containing personal data, or customer credentials.

8. Go-live checklist

  • Complete all KYC/KYB documents
  • Test success, failure, cancellation, expiry, and retry paths in sandbox
  • Make webhook handling signature-verified and idempotent
  • Store live secrets in a managed secret store
  • Configure alerting for failed webhooks and payouts
  • Reconcile settlements against your order ledger
  • Restrict production access by team role
  • Use HTTPS and verify your return URLs