TeronaPay

Payments API

The Payment object

{
  "id": "8d6ff4fa-da3d-4006-8997-d2780d0e22fe",
  "reference": "order-1234",
  "amount": 125.00,
  "currency": "KES",
  "wallet_id": "wlt_DEFAULT00001",
  "channel": "mpesa_stk_push",
  "status": "succeeded",
  "fee": 1.88,
  "net_amount": 123.12,
  "channel_receipt": "UEAL141VYG",
  "callback_url": "https://your-domain.com/callbacks/order-1234",
  "refunded_at": null,
  "refunded": 0.00,
  "refunds": [],
  "created_at": "2026-05-19T10:23:11.512Z",
  "updated_at": "2026-05-19T10:23:14.881Z"
}
FieldTypeNotes
iduuidInternal payment id
referencestringMerchant-supplied; unique per merchant
amountnumberGross amount in decimal major units (e.g. 125.00 = KES 125.00)
currencystringISO-4217
wallet_idstringWallet credited when the payment succeeds
channelenummpesa_stk_push / mpesa_c2b / card / bank_transfer / mobile_money
statusenumpending / processing / succeeded / failed / canceled
feenumberPlatform fee, decimal major units. Locked in at creation
net_amountnumberamount - fee
channel_receiptstringM-Pesa TransID, Stripe charge id, etc — channel-side reference
callback_urlstringPer-payment callback URL, if one was supplied at creation
refunded_attimestamp | nullSet when cumulative refund reaches amount
refundednumberCumulative refunded total (decimal major units)
refundsarrayAll refund slices (newest first)

POST /v1/payments

Create a payment. Channel-aware: required fields vary.

Common fields

FieldTypeRequiredNotes
referencestringUnique per merchant
amountnumberDecimal major units, positive (e.g. 125.00)
currencystringDefaults to merchant's default_currency
wallet_idstringWallet to credit on success — its wlt_… id or its 8-digit number. Defaults to the default wallet; its currency must match currency
channelenumDefaults to mpesa_stk_push
callback_urlstringAbsolute https:// URL. When set, this payment's payment.succeeded / payment.failed events are delivered only to this URL (overriding your registered webhook endpoints for this payment), HMAC-signed with your callback signing secret. Omit to fan out to registered endpoints

M-Pesa STK Push also requires:

FieldTypeNotes
payer_phonestringE.164 with +, e.g. +254712345678

Card also requires:

FieldTypeNotes
card_tokenstringTokenized card reference (Stripe payment-method id, etc)

Example — STK Push (endpoint fan-out)

curl -X POST https://api.teronapay.com/v1/payments \
  -u "$KEY:$SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1234" \
  -d '{
    "reference": "order-1234",
    "amount": 125.00,
    "currency": "KES",
    "channel": "mpesa_stk_push",
    "payer_phone": "+254712345678"
  }'

Example — STK Push with per-payment callback

curl -X POST https://api.teronapay.com/v1/payments \
  -u "$KEY:$SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-5678" \
  -d '{
    "reference": "order-5678",
    "amount": 200.00,
    "currency": "KES",
    "channel": "mpesa_stk_push",
    "payer_phone": "+254712345678",
    "callback_url": "https://your-domain.com/callbacks/order-5678"
  }'

When callback_url is set, payment.succeeded / payment.failed events go only to that URL (signed with your callback signing secret). Registered endpoints are skipped for this payment.

Response 201: a Payment object (status processing for async channels, succeeded/failed for sync).

Errors

  • 400 invalid_request — missing required field, wrong currency for channel, malformed phone
  • 401 unauthorized — bad API key
  • 429 rate_limited — write bucket exceeded (Retry-After header included)

GET /v1/payments

List payments. Newest first.

Query parameters

ParamTypeNotes
statusenumFilter to one status
channelenumFilter to one channel
referencestringExact match
sinceRFC3339created_at >= since
beforeRFC3339created_at < before — also the cursor for pagination
limitint1..100, default 25

Response 200:

{
  "data": [ /* Payment objects */ ],
  "next_before": "2026-05-19T10:20:00Z"
}

next_before is set when the current page is full. Pass it as ?before=... to fetch the next (older) page.

GET /v1/payments/{id}

Retrieve one payment. Includes the full refunds[] array.

Errors

  • 404 not_found — id doesn't exist or belongs to another merchant (we don't differentiate)

POST /v1/payments/{id}/refunds

Issue a refund. See Refunds API for full reference.

Status codes summary

CodeMeaning
200OK — GET succeeded
201Created — payment created
400Bad request
401Missing or invalid API key
404Payment not found
409Conflict (duplicate reference without idempotency key)
422Precondition failed (e.g. refunding a non-succeeded payment)
429Rate limit exceeded
500Internal error
API: Payments · TeronaPay docs