API Reference

Complete documentation of every endpoint in the HexAI Payment Gateway API. All endpoints are prefixed with /api/v1.

Base URL

One base URL serves both environments — your API key prefix selects which one you hit. There is no separate sandbox host to switch to.

Base URLhttps://api.hpg.hexai.gm/api/v1
sk_test_…

Sandbox — no real money moves; magic amounts trigger test outcomes.

sk_live_…

Production — real transactions against live rails.

Integrating each rail

HPG routes each payment to one of several underlying rails. Choose a rail per request with the optional provider field — omit it and HPG defaults to WAVE; an unknown or disabled provider returns 422 provider_not_enabled. The rails do not integrate the same way: Wave and Waychit redirect the customer, while APS is a two-step wallet + OTP charge with no redirect. Pick a rail below for its exact end-to-end flow.

Redirect checkoutPayouts supportedStatus: Webhook or poll
  1. 1

    Create the collection

    Call /collections/initiate. provider is optional — it defaults to WAVE. You get back a redirect_url and a PENDING status.

    Request

    curl -X POST https://api.hpg.hexai.gm/api/v1/collections/initiate \
      -H "Authorization: Bearer sk_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "amount": "10.50",
        "currency": "GMD",
        "client_reference": "ORD-12345",
        "customer_name": "Awa Ceesay",
        "customer_mobile": "+2207123456",
        "success_url": "https://yourapp.com/success",
        "error_url": "https://yourapp.com/failed"
      }'

    200Response

    {
      "status": "success",
      "data": {
        "transaction_id": "b3f1c2a4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
        "client_reference": "ORD-12345",
        "amount": "10.50",
        "currency": "GMD",
        "status": "PENDING",
        "provider": "WAVE",
        "redirect_url": "https://checkout.wave.com/c/cos-abc123",
        "next_action": null,
        "created_at": "2026-07-28T12:00:00.000Z"
      }
    }
  2. 2

    Send the customer to Wave

    Redirect the browser to data.redirect_url. The customer approves the payment in Wave, then returns to your success_url / error_url.

  3. 3

    Get the final result

    The redirect is not proof of payment. Trust the payment.succeeded / payment.failed webhook, or poll GET /collections/status/{client_reference}.

Payouts supported — disburse with POST /payouts/send.
The redirect only starts the payment; the webhook confirms it.
Set up your webhook handler

Sandbox Mode

Sandbox is the same API with fake money behind it — same endpoints, same responses, same webhooks — so your integration works unchanged when you go live. Reach it with a test key (sk_test_*), or flip your dashboard to Test mode with the toggle in the header to watch the same transactions arrive as you make them.

Magic amounts force a specific outcome, and the conventions are shared across every rail (Wave, APS and Waychit), so a sandbox suite written once behaves the same whichever provider you set:

Amount (GMD)OutcomeSimulated Behavior
100.00SUCCESSTransaction completes successfully
200.00PENDINGTransaction stays pending (test polling)
400.00FAILEDInsufficient funds error
403.00FAILEDAccount blocked error
408.00EXPIREDCheckout session times out before the customer pays
429.00FAILEDRate limit exceeded
499.00CANCELLEDCustomer abandons the checkout
500.00ERRORInternal server error (test retries)

EXPIRED and CANCELLED are not failures. Nothing was charged and nothing was refused — you simply lost the customer, so re-offering the same checkout is a valid next step. A FAILED collection was actually declined. The amounts echo the HTTP status they stand for, so the table is guessable: 408 Request Timeout, 499 client closed request.

Payouts draw on a per-rail float you fund yourself in Sandbox → Rail floats. A payout larger than the float is held as AWAITING_LIQUIDITY rather than failed — exactly what happens live when a rail is short.

Loading the live API spec…

Webhooks

HPG sends a single, provider-agnostic webhook to your endpoint whenever a transaction status changes — the payload is identical whether the payment ran over Wave, APS, or Waychit. This is especially important for APS and WAYCHIT_CARD, which have no redirect/polling flow: the webhook is your primary signal that money moved.

Payload Example

{
  "event": "payment.succeeded",
  "data": {
    "id": "tx_abc123",
    "type": "collection",
    "reference": "INV-001",
    "amount": "500.00",
    "currency": "GMD",
    "status": "SUCCEEDED",
    "provider": "WAVE",
    "environment": "LIVE",
    "completed_at": "2026-01-01T12:00:00.000Z"
  }
}

Headers HPG sends

  • x-hexai-signature — HMAC-SHA256 of the raw JSON body, keyed with your webhook secret
  • wave-signature — same value, kept as a legacy alias for existing Wave-style consumers
  • X-Hexai-Event — the event type (e.g. payment.succeeded)
  • X-Hexai-Sandbox — present and true only for sandbox traffic

Event types

These are every event HPG sends. Switch on event — names are past-tense resource.event and will not change. Note that a lost customer (expired, cancelled) is reported separately from a decline (failed), because only one of them is worth retrying as-is.

  • payment.succeeded — a collection settled
  • payment.failed — a collection was declined
  • payment.expired — the checkout session timed out before the customer acted; nothing was charged
  • payment.cancelled — the customer abandoned the checkout; nothing was charged
  • payout.succeeded — a payout reached the recipient
  • payout.failed — a payout could not be delivered
  • payout.pending — a payout is in flight, awaiting settlement
  • payout.reversed — a payout was reversed within the 3-day window

Verify x-hexai-signature by recomputing the HMAC-SHA256 of the exact request body with your webhook secret and comparing in constant time before you act on the event. The wave-signature header carries the same value, so existing verifiers keep working unchanged.

Error Handling

The API uses standard HTTP status codes and returns errors in a consistent JSON format.

Status CodeMeaningCommon Causes
200SuccessRequest completed successfully
400Bad RequestInvalid parameters, missing required fields, malformed JSON
401UnauthorizedMissing or invalid API key
403ForbiddenValid API key but insufficient permissions
404Not FoundResource doesn't exist (invalid transaction ID, etc.)
429Rate LimitedToo many requests in a short time
500Internal ErrorServer-side issue (retry with exponential backoff)
503Service UnavailableTemporary maintenance or overload

Error Response Format

All error responses follow this structure:

{
  "status": "error",
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Your account balance is insufficient for this payout.",
    "details": {
      "required_amount_bututs": 50000,
      "available_balance_bututs": 30000
    }
  }
}

Verification Examples

# Verify manually using openssl
echo -n '{"event":"payment.succeeded",...}' | \
openssl dgst -sha256 -hmac "YOUR_WEBHOOK_SECRET"