Debo Pay API Reference
The Debo Pay API is organized around REST. It has predictable resource-oriented URLs, accepts JSON request bodies, returns JSON responses, and uses standard HTTP verbs and status codes.
Debo Pay is your payment gateway
Integrate once with a single API key. Debo Pay exposes many payment methods under one gateway — you connect to Debo Pay only; you do not wire each bank or wallet API yourself. We route collections and payouts across 30+ Ethiopian banks and wallets (telebirr, M-PESA, CBE Birr, Ebirr, Kacha, EthSwitch banks, international cards, and more). Choose a payment_method on charge or let customers pick on hosted checkout; Debo Pay handles each rail behind the scenes.
Integration Guide
Full step-by-step walkthrough: get keys → initialize payment → redirect to checkout → verify webhooks → go live. Separate copy-paste paths for plain PHP, Laravel, and Node.js (Express).
- Create sandbox keys on Dashboard → API Keys.
- Call
POST /v1/payments/initializefrom your server with a requiredIdempotency-Key. - Redirect the customer to
checkout_url. - Verify
X-DeboPay-Signatureon your webhook, then mark the order paid. - Switch to
sk_live_/whsec_live_for production.
Plain PHP
SDK or raw cURL + webhook endpoint.
Laravel
Config, checkout controller, CSRF-exempt webhook.
Node.js
Express + raw body signature verify.
Authentication
DeboPay issues three keys per bundle (Dashboard → API Keys). Use the right key for the right layer:
| Key | Prefix | Where to use | Never |
|---|---|---|---|
| Publishable | pk_test_ / pk_live_ | Browser / mobile — identify your merchant for hosted checkout | Cannot call secret API routes alone |
| Secret | sk_test_ / sk_live_ | Server only — Authorization: Bearer sk_… for /v1/* |
Never ship in JS / public repos |
| Webhook | whsec_test_ / whsec_live_ | Verify callbacks with HMAC-SHA256 (X-DeboPay-Signature) |
Do not use as Bearer token |
- Create a bundle on Dashboard → API Keys.
- Reveal with account password — values stay until you leave the page.
- Store
sk_in server env,pk_in client env,whsec_in your webhook verifier. - Roll and Delete unused are password-gated. Delete only works if the key was never used.
How the three keys work together
- pk_ — Put in your website/app config so hosted checkout and client SDKs know which merchant is collecting. Safe to expose in public env vars.
- sk_ — Put only on your server (CI4
DEBOPAY_SECRET_KEY, LaravelDEBOPAY_SECRET_KEY, Node env). EveryPOST /v1/payments/initialize, charge, refund, and balance call usesAuthorization: Bearer sk_…. - whsec_ — When DeboPay POSTs payment events to your
callback_url, verifyX-DeboPay-Signature = HMAC-SHA256(rawBody, whsec_)before trusting the payload. Reject mismatches.
Typical flow: your server creates a payment with sk_ → customer pays on DeboPay checkout → DeboPay calls your webhook signed with whsec_ → you mark the order paid. Use pk_ only if you embed publishable widgets.
Roll rotates one key type (pk, sk, or whsec) after password confirm — update every integration that stored the old value. Delete unused removes a never-used bundle (all three) after password confirm. Used keys can only be revoked (disabled), not deleted.
Sandbox keys start with pk_test_ / sk_test_ / whsec_test_. Live keys use _live_. Never commit secrets to git.
Authenticate API requests with your secret key as a Bearer token. Legacy dp_test_ / dp_live_ secrets still work.
Idempotency (required on money-moving POSTs)
This is a targeted requirement on create/move-funds endpoints — not a full API rewrite. Retries with the same key are safe; missing keys return 422.
- Send
idempotency_keyin JSON or headerIdempotency-Key(either is accepted). - Required on:
POST /v1/payments/initialize,POST /v1/payments,POST /v1/charges, and wallet/v1/wallet/topup|transfer|bills. - Length 16–128; charset
[A-Za-z0-9_-]. New key per new business intent; reuse the same key only when retrying the same request. - Same merchant + same key → same payment resource. Duplicate merchant
referencewith a different key →409 duplicate_reference.
Create a key at Dashboard → API Keys
Each generate creates one card with Publishable (pk_), Secret (sk_), and Webhook (whsec_). Reveal / Roll / Delete require your account password.
curl https://debopay.com/v1/balance \
-H "Authorization: Bearer sk_test_sandbox_demo_secret_2026xx"
Webhook verification (whsec_)
DeboPay signs webhook bodies with HMAC-SHA256 using your whsec_… secret. Compare the X-DeboPay-Signature header to hash_hmac('sha256', \$rawBody, \$whsec) (PHP) / equivalent in Node, Laravel, CI4, Flutter backends.
curl -X POST https://yoursite.com/webhooks/debopay \
-H "Content-Type: application/json" \
-H "X-DeboPay-Signature: <hmac>" \
-d '{"event":"payment.success","data":{...}}'
Errors & Status Codes
Debo Pay uses conventional HTTP status codes. Errors return a consistent JSON envelope with a machine-readable code.
| Code | Meaning |
|---|---|
| 200 OK | Request succeeded |
| 201 Created | Resource created |
| 400 Bad Request | Invalid parameters |
| 401 Unauthorized | Missing or invalid API key |
| 404 Not Found | Resource does not exist |
| 409 Conflict | Duplicate reference or state conflict |
| 422 Unprocessable | Validation failed |
| 429 Too Many Requests | Rate limit exceeded |
{
"status": "error",
"error": {
"code": "duplicate_reference",
"message": "Reference already used for this merchant.",
"doc_url": "https://docs.debopay.com/errors/duplicate_reference"
}
}
/v1/payments/initialize
Create a Payment
Store an order on DeboPay from your site (phone, amount, order/reference). Returns checkout_url + debo_reference. Money-moving POSTs require idempotency: official SDKs auto-generate a valid key if you omit one, but for retry safety you should store a real key per order and reuse it on retries. Example: Abel · phone 0912121212 · amount 5000 · reference 716181. After pay, we webhook your callback_url (Stripe-style). Always send return_url for hosted checkout return.
curl -X POST https://debopay.com/v1/payments/initialize \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pay_716181_ab12cd34ef56gh78" \
-d '{"amount":5000,"currency":"ETB","reference":"716181","description":"Abel — order 716181","return_url":"https://yoursite.com/checkout/success","callback_url":"https://yoursite.com/webhooks/debopay","customer":{"phone":"0912121212","first_name":"Abel"}}'
POST /v1/payments/initialize
{
"status": "success",
"data": {
"status": "Success",
"reference": "DP240000001",
"transaction": "TRX-987654321",
"merchant": "Demo Coffee PLC",
"customer": "Abebe Kebede",
"amount": 250,
"fee": 3,
"currency": "ETB",
"created": "2026-07-17",
"settlement": "Completed",
"payment_method": "Wallet",
"checkout_url": "https://pay.debopay.com/checkout/DP240000001"
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
SuperApp pay by Order
SuperApp pay by Order lets any registered SuperApp (or DeboWallet) collect payment against your
order_id — like an airline PNR.
You create the order on DeboPay (POST /v1/payments/initialize or Business → Payment request)
with customer info + amount. The customer searches that order id in the SuperApp, confirms details, and pays.
DeboPay then hits your webhook with payment.success so your site marks the order complete.
For classic web checkout, initialize and redirect the browser to checkout_url, then return the user to your app — the webhook still updates your backend.
Example: Abel · phone 0912121212 · amount 5000 · order 716181 · detail xxx.
1. Your site/API: store the order on DeboPay (reference = order_id).
2. Customer: SuperApp / Wallet → Payment request → search 716181 → Confirm & pay.
3. Your webhook: receive signed payment.success → fulfill (same idea as airlines completing a PNR).
Portal simulation: Business → Payment request → amount 1402, order 1xxj, description Abel → pay from wallet → status Paid.
/v1/charges
Direct Charge
Charge a customer directly through a specific provider (mobile money or card) without the hosted checkout. This is also a money-moving POST, so use the same idempotency rules: SDK auto-generation is supported, but explicit stored keys are recommended for retries.
curl -X POST https://debopay.com/v1/charges \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: chg_001_ab12cd34ef56gh78" \
-d '{"amount":2500,"reference":"chg_001","payment_method":"telebirr","phone":"+251911223344"}'
POST /v1/charges
{
"status": "success",
"data": {
"debo_reference": "DPX-20260529-A1B2C3",
"amount": 2500,
"status": "success",
"payment_method": "telebirr",
"provider_reference": "TELEBIRR-MUSWSD7XGJ"
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
/v1/transactions/{reference}
Verify a Transaction
Retrieve a transaction by your reference or the debo_reference to confirm its status.
curl https://debopay.com/v1/transactions/order_001 \
-H "Authorization: Bearer sk_test_..."
{
"status": "success",
"data": {
"reference": "order_001",
"status": "success",
"amount": 2500,
"paid_at": "2026-05-29T08:12:55+00:00"
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
/v1/refunds
Refunds
Refund a successful payment fully or partially using the DeboPay transaction id (debo_reference from initialize/charge — e.g. DPX-20260717-A1B2C3). Do not use merchant order references, DeboFund campaign numbers, or system order IDs. Omit amount for a full refund.
curl -X POST https://debopay.com/v1/refunds \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"transaction_id":"DPX-20260717-A1B2C3","amount":500,"reason":"partial"}'
{
"status": "success",
"data": {
"debo_reference": "DPX-20260717-A1B2C3",
"status": "success",
"refunded_amount": 500,
"currency": "ETB"
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
/v1/balance
Balance
Retrieve your available balance, total fees charged, and current fee rate.
curl https://debopay.com/v1/balance -H "Authorization: Bearer sk_test_..."
GET /v1/balance
{
"status": "success",
"data": {
"available": 985.00,
"currency": "ETB",
"total_fees_charged": 15.00,
"fee_percent": 1.5
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
/v1/payouts
Payouts
Move funds from your Debo balance to any Ethiopian bank or wallet — instantly or on a schedule. Debo Pay handles local rail selection; you do not call external gateway APIs.
curl -X POST https://debopay.com/v1/payouts \
-H "Authorization: Bearer sk_test_..." \
-d '{"amount":500,"bank_code":"CBE","account_number":"1000123456789"}'
{
"status": "success",
"data": {
"reference": "PO-20260529-HBSUPH5G",
"amount": 500,
"status": "paid",
"bank_code": "CBE"
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
/v1/transfers/bulk
Bulk Transfers
Send up to 100 payouts in one request — ideal for salary payroll, donor refunds, or sweeping wallet float to your bank.
curl -X POST https://debopay.com/v1/transfers/bulk \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"title":"Donor refunds","currency":"ETB","transfers":[{"account_number":"1000123456789","bank_code":"CBE","account_name":"Abebe K","amount":500,"reference":"ref_001"}]}'
{
"status": "success",
"data": {
"batch_id": "BTX-20260608-ABC12345",
"title": "Donor refunds",
"currency": "ETB",
"total": 1,
"success": 1,
"failed": 0,
"results": [{"reference":"ref_001","account_number":"1000123456789","bank":"CBE","amount":500,"status":"success","transaction_id":"PO-20260608-XYZ"}]
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
Webhooks
Register HTTPS endpoints to receive events such as payment.success, refund.completed and payout.paid. Every request is signed with X-DeboPay-Signature (HMAC-SHA256 of timestamp.body). Always verify the signature before trusting an event.
# Header format
X-DeboPay-Signature: t=1780042391,v1=62f5a1...
# Recompute: HMAC_SHA256(secret, "{t}.{raw_body}")
# and compare with v1 using a constant-time check.
curl -X POST https://debopay.com/v1/webhooks/endpoints \
-H "Authorization: Bearer sk_test_..." \
-d '{"url":"https://shop.et/webhooks","events":["payment.success"]}'
/v1/subscriptions
Recurring Payments
Create a subscription plan to charge a customer automatically on a daily, weekly, monthly or yearly cadence — or schedule outbound salary payroll in Super App (pairs with POST /transfers/bulk on each payment day). The scheduler charges each cycle and fires subscription.charged / subscription.charge_failed webhooks.
curl -X POST https://debopay.com/v1/subscriptions \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"plan_name": "Pro monthly",
"amount": 499,
"currency": "ETB",
"interval": "monthly",
"interval_count": 1,
"total_cycles": 12,
"payment_method": "telebirr",
"customer": {"email": "buyer@shop.et", "phone": "+251911000111"}
}'
POST /v1/subscriptions
{
"status": "success",
"data": {
"reference": "sub_a1b2c3d4e5f6",
"plan_name": "Pro monthly",
"amount": 499,
"currency": "ETB",
"interval": "monthly",
"interval_count": 1,
"total_cycles": 12,
"completed_cycles": 0,
"status": "active",
"next_run_at": "2026-06-03T00:00:00+00:00"
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
Manage a subscription
Retrieve, pause, resume or cancel a plan by its reference. Pausing keeps the plan but stops charges until resumed; cancelling is permanent.
# List all subscriptions
curl https://debopay.com/v1/subscriptions -H "Authorization: Bearer sk_test_..."
# Retrieve one
curl https://debopay.com/v1/subscriptions/sub_a1b2c3d4e5f6 -H "Authorization: Bearer sk_test_..."
# Pause / resume / cancel
curl -X POST https://debopay.com/v1/subscriptions/sub_a1b2c3d4e5f6/pause -H "Authorization: Bearer sk_test_..."
curl -X POST https://debopay.com/v1/subscriptions/sub_a1b2c3d4e5f6/resume -H "Authorization: Bearer sk_test_..."
curl -X POST https://debopay.com/v1/subscriptions/sub_a1b2c3d4e5f6/cancel -H "Authorization: Bearer sk_test_..."
Parameters
| Field | Type | Description |
|---|---|---|
| plan_name | string | Human-readable plan label. Required. |
| amount | number | Amount charged each cycle. Required. |
| currency | string | 3-letter code. Defaults to ETB. |
| interval | string | daily, weekly, monthly or yearly. Required. |
| interval_count | integer | Number of intervals between charges. Defaults to 1. |
| total_cycles | integer | Stop after N charges. Omit for open-ended. |
| payment_method | string | Provider used for each charge (e.g. telebirr). |
| starts_at | datetime | When the first charge runs. Defaults to now. |
| customer.email / customer.phone | string | Customer contact for receipts & charges. |
Subscription webhook events
| Event | Fired when |
|---|---|
| subscription.charged | A cycle was charged successfully. |
| subscription.charge_failed | A cycle charge failed (retried on the next run). |
| subscription.completed | The final cycle of a capped plan was charged. |
| subscription.cancelled | The subscription was cancelled. |
/v1/fund/campaigns/{id}/donate
Debo Fund
Create crowdfunding campaigns and accept donations. Donations are charged through any supported provider.
curl -X POST https://debopay.com/v1/fund/campaigns/{slug}/donate \
-H "Authorization: Bearer sk_test_..." \
-d '{"amount":600,"payment_method":"telebirr","donor_name":"Abebe","donor_phone":"+251911000111"}'
{
"status": "success",
"data": {
"donation_id": "019e723e-fe78-...",
"amount": 600,
"status": "success"
}
}
{
"status": "error",
"error": {
"code": "invalid_api_key",
"message": "Authentication failed.",
"doc_url": "https://docs.debopay.com/errors/invalid_api_key"
}
}
SDKs & Libraries
Official libraries handle authentication, retries, auto-idempotency, and webhook verification for you. Every money-moving call (initializePayment, charge) auto-generates a 32-char idempotency key if you don't provide one. For retry safety, pass your own key — see the Idempotency section.
SDKs are self-hosted on api.debopay.com until Packagist / npm / PyPI / GitHub modules are published. PHP: run from a project directory (not ~ alone). See SDK manifest.
Idempotency
Money-moving POST endpoints require an idempotency key to prevent duplicate charges. If a request times out or you receive a 5xx, retry with the same key and same body — DeboPay returns the original result instead of creating a duplicate.
Required on
| Endpoint | Why |
|---|---|
| POST /v1/payments/initialize | Creates a payment intent — duplicate calls would create multiple orders |
| POST /v1/charges | Directly debits a customer |
| POST /v1/wallet/topup | Adds funds to a wallet |
| POST /v1/wallet/transfer | P2P or business transfer |
| POST /v1/wallet/bills | Bill / utility payment |
How to send
Send the key as an HTTP header or in the JSON body (either is accepted). For raw HTTP clients like cURL, fetch, axios, Java, C#, or mobile apps, the header is usually the cleanest option. For official SDKs, passing idempotency_key in params is the clearest pattern.
# Option A: HTTP header
curl -X POST https://debopay.com/v1/payments/initialize \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pay_716181_ab12cd34ef56gh78" \
-d '{"amount":5000,"currency":"ETB","reference":"716181",...}'
# Option B: JSON body field
curl -X POST https://debopay.com/v1/payments/initialize \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"amount":5000,"currency":"ETB","reference":"716181","idempotency_key":"pay_716181_ab12cd34ef56gh78",...}'
Key rules
| Rule | Detail |
|---|---|
| Length | 16–128 characters |
| Charset | [A-Za-z0-9_-] |
| New intent | New order / new payment → new key (and usually new reference) |
| Retry | Timeout, 5xx, double-click → same key + same body |
| TTL | Keys expire after 24 hours |
| Missing / short | Returns 422 validation error |
Idempotency vs Reference
| Field | Owner | Purpose |
|---|---|---|
| reference | Merchant | Your order ID — must be unique per new payment intent |
| idempotency_key | Client | Prevents duplicate execution of the same HTTP request |
Same merchant + same key → returns original result (200/201). Same reference + different key → 409 duplicate_reference.
SDK auto-idempotency
All official SDKs (PHP, Node, Python, Go) support idempotency. PHP, Node, and Python auto-generate a valid key if you omit idempotency_key; Go examples should pass it explicitly. For retry safety in every language, store the key on your order and pass it explicitly so retries reuse the same key.
Best practice: Generate the key when the order is created, store it in your database, and pass it on every attempt. This way network retries, user double-clicks, and queue re-deliveries all resolve to the same payment.
Rate Limits
Sandbox is limited to 100 requests/minute; production limits scale with your tier. Responses include X-RateLimit-Remaining. Exceeding the limit returns 429 Too Many Requests — back off and retry with exponential delay.