Build on PowerWave
Sell airtime, data and bills, collect payments and send payouts from your own website or app with the PowerWave API. Start here: access, authentication, errors and limits.
Getting started
The PowerWave API is organised in four parts. Pick the one you need - each has its own page with copy-ready requests and responses.
https://api.powerwave.ng/v1 · JSON in, JSON out · HTTPS only · live keys only (every purchase, payment and payout is real) - test with small amounts.Getting access
- Create an account and verify to Tier 2 (Tier 3 for Payments and Payouts).
- Open the Reseller page and add a reseller business (one per website or app). Save it as a draft and finish it later if you like, then submit it - there is no separate application. Our team reviews each business, and the first one approved makes you a reseller.
- Open the business → API & webhooks, create a key (shown once) and optionally lock it to your server's IP. Each key belongs to one business: its references, checkouts, customers, webhooks and
/transactionsare kept separate from your other businesses. All businesses share your one wallet. - Fund your wallet - purchases and payouts are paid from it.
- Payouts only: ask support to approve payout access, then create a separate payout key (Reseller → Payout API) with your server IPs. See Payouts.
Authentication
Send your key in the Authorization header on every request. Never put a key in a website, mobile app or anything a customer can download - call the API from your server only.
| Key | Looks like | Used for |
|---|---|---|
| Reseller API key | pw_live_ + 40 characters | Purchases, Payments, /transactions. Per business, optional IP lock. |
| Payout key | pw_payout_ + 40 characters | Payouts only. One per reseller, IP allow-list required. |
curl https://api.powerwave.ng/v1/ping \ -H "Authorization: Bearer pw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Requests & responses
POST bodies are JSON (Content-Type: application/json). Every response has the same shape:
{
"success": true,
"message": "Purchase successful.",
"data": {
"…": "…"
}
}{
"success": false,
"message": "customer_email is required (the customer's email address).",
"data": {},
"error": {
"code": "validation_error"
}
}Amounts are strings in naira with 2 decimals. Dates are ISO 8601 in West Africa Time (+01:00). Responses are never cached.
References & safe retries
Every purchase, checkout and payout needs a reference that is unique in your system (letters, numbers, . - _) - use your own order ID.
- Same reference + same body (e.g. after a timeout): you get the original result back, header
Idempotent-Replay: true. You are never charged twice. - Same reference + different body:
409 idempotency_conflict. - If nothing was charged (validation error, insufficient balance) the reference is released and you can retry with it.
Errors
| HTTP | error.code | Meaning |
|---|---|---|
| 400 | invalid_json | Body is not valid JSON. |
| 401 | unauthorized | Missing, malformed, invalid or revoked key. |
| 402 | insufficient_balance | Wallet balance too low - nothing was charged. Fund your wallet and retry with the same reference. |
| 403 | ip_not_allowed / reseller_inactive / business_inactive / api_access_revoked / tier_too_low / account_restricted | Your key is valid but not allowed right now (business_inactive: that reseller business is a draft, pending, rejected or suspended). |
| 403 | gateway_unavailable | Payments need an approved reseller business at Tier 3 with payments switched on. |
| 404 | not_found | Unknown endpoint or reference. |
| 409 | idempotency_conflict | That reference was used for a different request. |
| 409 | request_in_progress | That reference is still processing - poll the GET endpoint for it. |
| 422 | validation_error | A field is missing or invalid; message says which (for example customer_email is required). |
| 429 | rate_limited | Too many requests; see Retry-After. |
| 200 | transaction_failed | The purchase was processed but failed at the provider; it has been refunded to your wallet (success=false, data.status=failed). |
| 500 / 503 | server_error / service_unavailable | Our side or a service is down. For purchases and payouts, check the reference before retrying. |
Rate limits
Each key allows 60 requests per minute by default (header X-RateLimit-Limit). Above it you get 429 with Retry-After. The payout key allows 30 per minute. Need more? Contact support.
Go-live checklist
- Keys stored server-side (environment variables or a secrets manager), never in client code or git.
- Keys locked to your server's IP address(es) (required for the payout key).
- A unique
referencefor every order; on timeouts, retry with the same reference. - Handle
pending/processing: wait for the webhook or poll - never buy or pay out again. - Webhook signatures verified and events de-duplicated by
id. See Webhooks. - Payments: always send the customer's email (and phone for customer account numbers); deliver only after
checkout.paidis verified. - Low-balance monitoring with
GET /balance, topped up throughGET /funding-account.
Questions? Contact us.
