Appia Gateway · Integration
Bank-transfer checkout in an afternoon.
One key. Four things to call. Your reference is the idempotency key, every read is by your reference, and the webhook signature is HMAC-SHA512 with a published test vector you can check against before you write a line.
Read this first
There is no sandbox yet.
Appia Gateway has live keys only. Every initialize
call creates a real virtual account and expects real money. We test with you — small-value
live transactions against your live key, coordinated with our team during onboarding.
Two more things we’d rather you hear from us
account_nameis required on initialize but isn’t used. The virtual account is always created asCHECKOUT.- Webhooks carry no timestamp header. Your de-duplication on
X-Appia-Delivery-Idis your replay protection.
We’d rather tell you than fake a sandbox badge.
01 — Auth
One key.Shown once.
Authorization: Bearer sk_live_… on every
/merchant/* request.
- Key format
sk_live_+ 64 hex = 72 chars- At rest
- bcrypt hash + a 12-char lookup prefix. Never readable again.
- Active keys
- Exactly one. Generating a new key invalidates the old one immediately.
- Errors
- 401 bad key · 403 pending/suspended · 503 collections off
02 — Quickstart
Five steps to live.
-
1
POST /merchant/initialize
Send
amount, yourreferenceandaccount_name. Returns the account number, bank (Providus Bank), amount, fee,fee_bearer,expires_at(30 min) and the Appia reference. 201 for a new charge, 200 when you replay a reference you already used. -
2
Render it yourself
In your checkout, with your own countdown. There is no hosted page, no widget and no redirect — which is exactly why nothing breaks between your page and the payment.
-
3
collection.successful
Verify the signature over the raw body, de-duplicate on
X-Appia-Delivery-Id, then fulfil the order. -
4
GET /merchant/transaction/:ref
Everything is queryable by your reference, so you never have to store an Appia ID to reconcile.
-
5
split_code
Optionally attach one at initialize, and every beneficiary is paid at 09:00 WAT the next morning.
03 — The call
Open a charge.
Request
curl -X POST https://api.appiawave.com/merchant/initialize \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "amount": 25000, "reference": "order_8812", "account_name": "Ada Obi" }'
Response 201
{
"account_number": "9901234567",
"bank_name": "Providus Bank",
"amount": 25000,
"fee": 375,
"fee_bearer": "merchant",
"expires_at": "2026-07-26T14:32:00Z",
"reference": "order_8812"
}
04 — Surface
The whole API,on one screen.
Collections
- POST /merchant/initialize
- GET /merchant/transaction/:ref
- GET /merchant/transactions
Filter by limit, offset, status, start_date, end_date.
Splits
- GET /merchant/splits
- POST /merchant/splits
- PUT /merchant/splits/:code
- DEL /merchant/splits/:code
Returns SPL_xxxxxxxx. A PUT replaces the beneficiary list and re-verifies every account.
Helpers
- GET /merchant/banks
- POST /merchant/banks/resolve
Resolve returns the real registered account name before you save a beneficiary.
Money out
- POST /merchant/payouts/trigger
- GET /merchant/payouts
- GET /merchant/payout/:batch_ref
- GET /merchant/balance
Send {"dry_run": true} for an exact preview with zero side effects.
05 — Idempotency
Your reference is the idempotency key.
Re-initializing an existing reference returns 200 with the
same account number, amount, expiry and current status. Never a duplicate charge. A database
unique constraint on (reference, merchant_id) catches
two identical calls arriving at the same instant. There is no separate
Idempotency-Key header to learn.
06 — Webhooks
Signed, retried, and verifiable before you trust us.
- Signature
- HMAC-SHA512, 128 lowercase hex
X-Appia-Signature - Signed over
- the raw request body
- Secret
whsec_+ 64 hex- Retries
- 1m · 5m · 30m · 2h · 12h
5 attempts, 10s timeout each - De-duplicate on
X-Appia-Delivery-Id- Verifiers
- Node · Python · PHP · Laravel
Published test vector
# secret
whsec_test
# body
{"event":"ping","data":{"ok":true}}
# expected X-Appia-Signature
70ed613dc2579b5b8961d9eb168c4d26a7d6f91d869af7c65
49ac403bcbe902662a33cd7589297c227e78e0259724da33f
17168758f56148f91bb0fbe6716376
Run your verifier against that before you point it at production. In the portal you can set the URL, watch delivery stats and success rate, browse logs, rotate the secret and re-send a failed delivery.
07 — Limits & responses
Numbers, not promises.
Rate limit
100 / 60s
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
429 adds Retry-After
Pagination
50 default · 100 max
currentPage · totalPages
totalRecords · hasNext · hasPrev
Status codes
- 201 new charge
- 200 replay / dry-run
- 400 validation · 401 auth
- 403 not active · 404 unknown ref
- 409 payout in progress
- 422 payout not processable
- 502 provider · 503 collections off
Onboarding is human-assisted. On purpose.
We onboard merchants with a CAC number and activate the account ourselves. That is why the button says Book a demo and not Sign up free — a real person walks your first live transaction with you.