CAPSEND MTO API

The CAPSEND MTO API lets partners create payout transactions and retrieve transaction status from their backend systems.

Environments

Set your integration base URL as an environment variable and use it in every request.

Environment BASE_URL Use for
Staging https://staging.capsendimto.org/v1 Testing API credentials, request formatting, webhook handling, and transaction status flows.
Production https://prod.capsendimto.org/v1 Live integrations and real transactions.
# Staging
BASE_URL="https://staging.capsendimto.org/v1"

# Production
BASE_URL="https://prod.capsendimto.org/v1"

API Client Setup

Create API clients from the CAPSEND MTO platform before calling the API. API clients are environment-scoped:

  1. Create a staging API client first and use it with the staging BASE_URL while building and testing.
  2. Configure the webhook URL that should receive staging events.
  3. Store the generated clientId and clientSecret securely. The secret should only be shown once.
  4. When your integration is ready for live processing, create a separate production API client on the MTO platform.
  5. Use production credentials only with the production BASE_URL. Production calls create real transactions.

Request Basics

Content type

Send request bodies as JSON.

Content-Type: application/json

Authentication

Authenticated endpoints require a bearer token from the Token endpoint.

Authorization: Bearer ACCESS_TOKEN

Status Codes

Code Meaning
200 Request completed successfully
400 Invalid request payload or missing required field
401 Missing, invalid, or expired token
404 Resource not found
409 Duplicate or conflicting resource
500 Internal server error

Error Response

{
  "message": "Missing or invalid token",
  "code": "UNAUTHORIZED",
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Token

Generate an access token using the API client credentials for the environment you are calling.

POST /api/token

Body Parameters

Parameter Type Required Description
clientId string Yes API client ID issued by CAPSEND.
clientSecret string Yes API client secret issued by CAPSEND.

Sample Request

curl -X POST "${BASE_URL}/api/token" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "capcid_1234567890abcdef",
    "clientSecret": "your_client_secret"
  }'

Sample Response

{
  "accessToken": "eyJhbGciOi...",
  "tokenType": "Bearer",
  "expiresIn": 86400
}

Create Transaction

Create a payout transaction. CAPSEND validates the MTO wallet balance, places a hold, and processes the transaction asynchronously.

POST /transactions/createTransaction

Staging Test Rules

When using the staging BASE_URL, transaction requests are for testing only:

Rule Value
Fixed transaction amount 100
Non-Zenith test bank Sudo Bank
Non-Zenith test bank code 999998
Non-Zenith test account number 0000014575

Use the Sudo Bank details only when testing transfers where the beneficiary bank is not Zenith Bank. Production transactions should use real beneficiary details approved for your integration.

Headers

Header Type Required Description
Authorization string Yes Bearer ACCESS_TOKEN
Content-Type string Yes application/json
idempotency-key uuid Yes Unique UUID for safely retrying the same request.

Body Parameters

Parameter Type Required Description
amount number Yes Transaction amount. In staging, this must be 100.
currency string Yes 3-letter wallet currency.USD, GBP, EUR.
transactionType string Yes PAYOUT.
crAccount string Yes Destination account number. For staging non-Zenith tests, use 0000014575.
narration string Yes Transfer narration.
country string Yes Sender country.
senderAddress string Yes Sender address.
senderName string Yes Sender full name.
beneficiaryBankName string Yes Destination bank name.
beneficiaryName string Yes Beneficiary full name.
beneficiaryAddress string Yes Beneficiary address.
stateCode string Yes Sender state code.
description string No Transfer description.
beneficiaryBankCode string Conditional Required for non-Zenith bank transfers. For staging non-Zenith tests, use 999998.
externalReference string No Partner reference.

Sample Request

curl -X POST "${BASE_URL}/transactions/createTransaction" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "idempotency-key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "amount": 100,
    "currency": "USD",
    "transactionType": "PAYOUT",
    "crAccount": "0000014575",
    "narration": "Staging test payout for INV-1001",
    "country": "NGN",
    "senderAddress": "100 Market Street, San Francisco",
    "senderName": "Jane Sender",
    "beneficiaryBankName": "Sudo Bank",
    "beneficiaryBankCode": "999998",
    "beneficiaryName": "John Beneficiary",
    "beneficiaryAddress": "12 Marina Road, Lagos",
    "stateCode": "LA",
    "externalReference": "INV-1001"
  }'

Sample Response

{
  "transactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "reference": "CAP17140590000001",
  "status": "PENDING"
}

Transaction Status Values

Status Description
PENDING Transaction has been created and queued for processing.
PROCESSING Transaction is being processed.
PROCESSED Transaction has been processed.
SUCCESS Transaction completed successfully.
FAILED Transaction failed.
REFUNDED Transaction has been refunded.

Fetch Transaction

Retrieve one transaction by CAPSEND transaction ID.

GET /transactions/get/:id

This is the public API route for partner integrations. It uses the API client bearer token returned by POST /api/token. Dashboard transaction routes use user authentication and should not be used for server-to-server API integrations.

Headers

Header Type Required Description
Authorization string Yes Bearer ACCESS_TOKEN

Path Parameters

Parameter Type Required Description
id string Yes CAPSEND transaction ID returned from Create Transaction.

Sample Request

curl "${BASE_URL}/transactions/get/7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -H "Authorization: Bearer ACCESS_TOKEN"

Sample Response

{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "reference": "CAP17140590000001",
  "status": "SUCCESS",
  "createdAt": "2026-04-25T10:00:00.000Z",
  "failureReason": null,
  "processedAt": "2026-04-25T10:00:05.000Z",
  "succeededAt": "2026-04-25T10:00:08.000Z",
  "failedAt": null
}

Webhooks

CAPSEND sends webhook events to the webhook URL configured for your API client environment. Every webhook request is signed so your server can verify that the payload came from CAPSEND and was not changed in transit.

Webhook Headers

Header Description
x-webhook-id CAPSEND webhook configuration ID that sent the event.
x-webhook-signature HMAC SHA-256 signature in the format sha256=<hex>.
x-webhook-timestamp Unix timestamp in seconds. Reject requests older than 5 minutes.

Event Payload

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "transaction.processed",
  "createdAt": "2026-04-25T10:00:08.000Z",
  "data": {
    "transactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "reference": "CAP17140590000001",
    "amount": 100,
    "currency": "USD"
  }
}

Events

Event Description
transaction.processed Transaction completed processing successfully.
transaction.failed Transaction failed.

Verification

Use this process in any programming language:

  1. Read and preserve the raw request body exactly as received, before parsing JSON or formatting it again.
  2. Read x-webhook-signature and x-webhook-timestamp from the request headers.
  3. Parse x-webhook-timestamp as Unix seconds and reject the request if it is more than 300 seconds from your server time.
  4. Compute an HMAC SHA-256 digest using your webhook secret as the key and the raw request body as the message.
  5. Hex-encode the digest in lowercase and prefix it with sha256=.
  6. Compare that expected value with x-webhook-signature using a constant-time comparison.
  7. Parse and process the JSON event only after verification succeeds.

Language-neutral pseudocode:

raw_body = request.raw_body
received_signature = request.header["x-webhook-signature"]
timestamp = request.header["x-webhook-timestamp"]

if abs(current_unix_seconds() - parse_integer(timestamp)) > 300:
  reject_request()

digest = hmac_sha256_hex(secret = webhook_secret, message = raw_body)
expected_signature = "sha256=" + digest

if not constant_time_equal(received_signature, expected_signature):
  reject_request()

event = parse_json(raw_body)
process_event_once(event.id)
return 200

Respond with 200 OK after receiving the event.