Appiawave

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_name is required on initialize but isn’t used. The virtual account is always created as CHECKOUT.
  • Webhooks carry no timestamp header. Your de-duplication on X-Appia-Delivery-Id is 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. 1

    POST /merchant/initialize

    Send amount, your reference and account_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. 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. 3

    collection.successful

    Verify the signature over the raw body, de-duplicate on X-Appia-Delivery-Id, then fulfil the order.

  4. 4

    GET /merchant/transaction/:ref

    Everything is queryable by your reference, so you never have to store an Appia ID to reconcile.

  5. 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.