Skip to content
EchoGateway

Developer documentation · v1

EchoGateway API

Integrate your website, app or ERP with EchoGateway: create transactions, send payers to a branded checkout, receive signed webhooks and pull settlement reports.

Base URL: https://echogateway.ng/api/v1 JSON over HTTPS HMAC-SHA512 signed

Overview

Every approved merchant gets its own public key, secret key and webhook secret under API & Webhooks in the dashboard (keys appear once your business verification is approved). You use them to:

  • initialize a payment and redirect the payer to the hosted checkout, which shows your merchant's logo and the payment channels enabled for your business;
  • receive a signed server-to-server webhook when the payment succeeds, fails or is reversed;
  • verify the payment before giving value, and pull transaction and settlement reports.

You never talk to Paystack, Flutterwave, Monnify, Remita, Interswitch, Squad, Korapay, OPay or CoralPay directly — the gateway handles the processors, fees, split settlement and reconciliation.

No system to integrate? Use payment links from the portal instead — no code needed.

Quick start

  1. Sign in to the merchant portal → API & Webhooks. Copy your public key and secret key, and set your callback URL (webhook endpoint) and return URL.
  2. From your server, call POST /transactions/initialize with the amount, your own unique reference and the payer's details.
  3. Redirect the payer to the checkout_url in the response.
  4. Handle the webhook at your callback URL (verify the signature, respond 200).
  5. Before giving value, call GET /transactions/{reference}/verify and check status = "successful" and the amount.

Authentication

Every API request must include four headers. Requests are only accepted over HTTPS, from whitelisted IPs if your merchant has an IP whitelist, and within 5 minutes of the timestamp.

HeaderValue
X-EG-Public-KeyYour public key, e.g. pk_live_…
AuthorizationBearer sk_live_… (your secret key)
X-EG-TimestampCurrent Unix time in seconds, e.g. 1793622532
X-EG-Signaturehex( HMAC_SHA512( timestamp + "." + raw_body, secret_key ) ) — for GET requests raw_body is an empty string
Content-Typeapplication/json (POST requests)

Every request needs a fresh timestamp and signature: a signature that has already been used is rejected as a replay (HTTP 401). Sign the exact bytes you send as the body. Keep your secret key on your server only — never in a browser or mobile app. If it leaks, regenerate keys from the portal; old keys stop working immediately.

// PHP
$ts   = (string) time();
$body = json_encode($payload);
$sig  = hash_hmac('sha512', $ts . '.' . $body, $secretKey);

Payment flow

  1. Your server → POST /transactions/initialize → receives reference and checkout_url.
  2. Payer is redirected to checkout_url, sees your logo and amount, chooses a channel (card, transfer, USSD, wallet, Remita or PayDirect at a bank…) and pays.
  3. The gateway confirms the payment with the processor server-side (never trusting browser parameters).
  4. The gateway sends a signed transaction.successful webhook to your callback URL and redirects the payer to your return URL.
  5. You verify and give value. Funds are split and settled to your bank account on your settlement cycle.

PayDirect / Remita: the payer may complete payment hours later at a bank branch. Keep the order open and rely on the webhook (or poll verify) rather than the browser return.

Initialize a transaction

POST https://echogateway.ng/api/v1/transactions/initialize

FieldTypeRequiredDescription
amountnumberYesAmount in Naira, e.g. 45000.00. This is what your merchant is owed; any processor fee is added on top when your fee bearer is customer.
merchant_referencestring ≤100YesYour unique reference (invoice/order ID). Re-using it returns the existing transaction (idempotent).
customer.namestringYesPayer's full name.
customer.emailemailYesReceipt is emailed here.
customer.phonestringNoPayer's phone number.
descriptionstring ≤255NoShown on checkout and receipt, e.g. "Order #10234 – 2 items".
processorstringNoSkip channel selection by pre-selecting a processor code (see processors).
callback_urlurlNoOverride your default webhook URL for this transaction.
return_urlurlNoWhere the payer is sent after payment (overrides default).
metadataobjectNoAny JSON you want echoed back (matric number, session, fee type…).
{
  "amount": 45000,
  "merchant_reference": "INV-2026-000123",
  "description": "2026/2027 Tuition - 200 Level",
  "customer": { "name": "Amina Yusuf", "email": "amina@example.com", "phone": "08031234567" },
  "metadata": { "matric_no": "CSC/2024/123", "session": "2026/2027" }
}

Response 201 Created

{
  "status": true,
  "message": "Transaction initialized",
  "data": {
    "reference": "EGW261027K3J9QX2L1M",
    "merchant_reference": "INV-2026-000123",
    "status": "pending",
    "amount": 45000,
    "fee": 0,
    "fee_bearer": "customer",
    "total_amount": 45000,
    "currency": "NGN",
    "processor": null,
    "checkout_url": "https://echogateway.ng/pay/EGW261027K3J9QX2L1M",
    "customer": { "name": "Amina Yusuf", "email": "amina@example.com", "phone": "08031234567" },
    "metadata": { "matric_no": "CSC/2024/123", "session": "2026/2027" },
    "created_at": "2026-10-27T10:15:22+01:00"
  }
}

Redirect the payer to data.checkout_url. fee and total_amount are updated once the payer picks a channel.

Verify a transaction

GET https://echogateway.ng/api/v1/transactions/{reference}/verify

Accepts either the gateway reference or your merchant_reference. If still pending, the gateway re-queries the processor before answering. Always verify before giving value, and confirm data.status == "successful" and data.amount matches what you expect.

{
  "status": true,
  "message": "Transaction successful",
  "data": {
    "reference": "EGW261027K3J9QX2L1M",
    "merchant_reference": "INV-2026-000123",
    "status": "successful",
    "amount": 45000, "fee": 675, "fee_bearer": "customer", "total_amount": 45675,
    "processor": "paystack", "channel": "card", "processor_reference": "4099260516",
    "paid_at": "2026-10-27T10:17:03+01:00",
    "settlement_status": "unsettled",
    ...
  }
}

Get / list transactions

GET https://echogateway.ng/api/v1/transactions/{reference} — returns the stored record without re-querying.

GET https://echogateway.ng/api/v1/transactions?status=successful&from=2026-10-01&to=2026-10-31&per_page=50&page=1

{ "status": true, "data": [ { ...transaction... } ], "meta": { "page": 1, "per_page": 50, "total": 1284, "last_page": 26 } }

For GET requests with a query string, sign an empty body: HMAC(timestamp + ".", secret).

Payment processors

GET https://echogateway.ng/api/v1/processors — channels currently enabled for your merchant.

CodeProcessorNotes
paystackPaystackCards, bank transfer, USSD and bank via Paystack checkout.
flutterwaveFlutterwaveCards, transfer, USSD and more via Flutterwave Standard.
remitaRemitaRemita Retrieval Reference (RRR) - pay online or at any bank branch.
interswitchInterswitch Web CheckoutVerve, Mastercard, Visa, transfer and USSD via Interswitch Webpay.
paydirectInterswitch PayDirectPay at any bank branch or Quickteller using the payment reference.
coralpayCoralPay VergeC'Gate USSD, cards, NQR and bank transfer via CoralPay Verge.
monnifyMonnifyCards, bank transfer (dedicated account), USSD and phone number via Monnify by Moniepoint.
squadSquad by GTCOCards, bank transfer, USSD and GTBank account payments via Squad.
korapayKorapayCards, bank transfer and Pay with Bank via Korapay checkout.
echomoneyBank Transfer (NET MFB)Pay by transfer from any bank into a one-time NET Microfinance Bank account number (EchoMoney).
opayOPayOPay wallet, cards, bank transfer and USSD via OPay Cashier.

Settlements

Successful payments are split (platform commission, partner commission, other beneficiaries, remainder to your account) and batched into settlements on your cycle — Instant, T+0, T+1, weekly or custom.

GET https://echogateway.ng/api/v1/settlements — list settlement batches with destination accounts.

GET https://echogateway.ng/api/v1/settlements/{reference} — one batch, its destination accounts and the transactions it covers.

{
  "status": true,
  "data": {
    "reference": "STL261028AB12CD34", "status": "settled", "cycle": "t1",
    "transactions_count": 482, "gross_amount": 21690000, "fees_amount": 0, "net_amount": 21690000,
    "settled_at": "2026-10-28T11:02:10+01:00",
    "accounts": [
      { "beneficiary": "Ade Fashion Stores", "type": "merchant", "bank": "Guaranty Trust Bank", "account_number": "0123456789", "amount": 20605500, "status": "paid", "payout_reference": "NIP-88210" },
      { "beneficiary": "EchoGateway (platform commission)", "type": "platform", "amount": 1084500, "status": "paid" }
    ],
    "transactions": [ { "reference": "EGW…", "merchant_reference": "INV-…", "amount": 45000, "paid_at": "…" } ]
  }
}

Webhooks (callbacks)

When a transaction changes status the gateway sends a POST with a JSON body to your callback URL.

EventWhen
transaction.successfulPayment confirmed by the processor
transaction.failedPayment declined, failed or amount mismatch
transaction.reversedPayment reversed (e.g. PayDirect reversal)
test.pingSent from the portal's "Send test webhook" button

Headers

X-EG-Event:      transaction.successful
X-EG-Timestamp:  1793112456
X-EG-Signature:  hex( HMAC_SHA512( timestamp + "." + raw_body, webhook_secret ) )

Body

{ "event": "transaction.successful", "data": { ...same shape as the verify response... } }

Verifying (PHP)

$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_EG_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_EG_SIGNATURE'] ?? '';
$expected = hash_hmac('sha512', $ts . '.' . $raw, getenv('EG_WEBHOOK_SECRET'));
if (!hash_equals($expected, $sig) || abs(time() - (int) $ts) > 300) { http_response_code(401); exit; }
$event = json_decode($raw, true);
// Look up your order by $event['data']['merchant_reference'], call /verify, then give value once.
http_response_code(200);

Respond with any 2xx within 20 seconds. Non-2xx responses and timeouts are retried after 1 min, 5 min, 15 min, 1 h and 2 h (6 attempts). Webhooks can arrive more than once — make your handler idempotent. Delivery history is visible in the portal.

Return URL

After payment the payer sees a gateway receipt with a Return to … button that opens your return URL with:

https://your-portal/return?reference=EGW261027K3J9QX2L1M&merchant_reference=INV-2026-000123&status=successful

These parameters are for display only — always confirm with /verify or the webhook.

Statuses & errors

Transaction statusMeaning
PendingCreated; awaiting payment or confirmation
SuccessfulPaid and confirmed — give value
FailedDeclined / failed / amount mismatch
AbandonedExpired without payment
ReversedReversed after success
HTTPMeaning
200 / 201OK / created
401Missing/invalid keys, timestamp or signature
403Merchant suspended or IP not whitelisted
404Transaction / settlement not found
409merchant_reference reused with a different amount
422Validation failed — see errors
429Rate limit (120 requests/minute)
{ "status": false, "message": "Validation failed", "errors": { "customer.email": ["The customer.email field must be a valid email address."] } }

Settlement status on a transaction: unsettled → batched (in a pending settlement) → settled.

Fees & splits

Each processor has a fee (percentage + flat, optionally capped) shown on the pricing page. Your business's fee bearer decides who pays it:

  • Customer — the fee is added to the amount at checkout; you receive the full amount before splits.
  • Merchant — the payer pays amount; the fee is deducted before splits.

Split rules (platform commission, partner commission, other beneficiaries) are then applied in order as fixed amounts or percentages, and the remainder goes to your settlement account. Each transaction's split lines are visible in the portal.

Payouts are automatic. When the payer chooses Paystack, Flutterwave, Monnify, Remita or Interswitch, the gateway sends the split to the processor with the payment, and the processor pays each beneficiary account directly on its normal settlement schedule. PayDirect payments settle according to the split configured with Interswitch. CoralPay, Squad, Korapay and OPay have no split settlement, so Echo Systems pays those out to you in settlement batches. The settlement_status and the settlement endpoints show which accounts were paid.

Code samples

<?php
function eg(string $method, string $path, ?array $payload = null): array {
    $pk = getenv('EG_PUBLIC_KEY'); $sk = getenv('EG_SECRET_KEY');
    $body = $payload ? json_encode($payload) : '';
    $ts = (string) time();
    $ch = curl_init('https://echogateway.ng/api/v1' . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS => $body ?: null,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json', 'Accept: application/json',
            'X-EG-Public-Key: ' . $pk,
            'Authorization: Bearer ' . $sk,
            'X-EG-Timestamp: ' . $ts,
            'X-EG-Signature: ' . hash_hmac('sha512', $ts . '.' . $body, $sk),
        ],
    ]);
    $res = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return $res;
}

$init = eg('POST', '/transactions/initialize', [
    'amount' => 45000, 'merchant_reference' => 'INV-2026-000123',
    'customer' => ['name' => 'Amina Yusuf', 'email' => 'amina@example.com'],
]);
header('Location: ' . $init['data']['checkout_url']);

// later
$v = eg('GET', '/transactions/INV-2026-000123/verify');
if ($v['data']['status'] === 'successful' && $v['data']['amount'] == 45000) { /* give value */ }
const crypto = require('crypto');

async function eg(method, path, payload) {
  const body = payload ? JSON.stringify(payload) : '';
  const ts = Math.floor(Date.now() / 1000).toString();
  const sig = crypto.createHmac('sha512', process.env.EG_SECRET_KEY).update(ts + '.' + body).digest('hex');
  const res = await fetch('https://echogateway.ng/api/v1' + path, {
    method,
    headers: {
      'Content-Type': 'application/json', Accept: 'application/json',
      'X-EG-Public-Key': process.env.EG_PUBLIC_KEY,
      Authorization: 'Bearer ' + process.env.EG_SECRET_KEY,
      'X-EG-Timestamp': ts, 'X-EG-Signature': sig,
    },
    body: body || undefined,
  });
  return res.json();
}

const init = await eg('POST', '/transactions/initialize', {
  amount: 45000, merchant_reference: 'INV-2026-000123',
  customer: { name: 'Amina Yusuf', email: 'amina@example.com' },
});
// res.redirect(init.data.checkout_url)

// Webhook (Express): use express.raw({ type: 'application/json' }) so you sign the raw bytes
app.post('/echogateway/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-EG-Timestamp');
  const expected = crypto.createHmac('sha512', process.env.EG_WEBHOOK_SECRET).update(ts + '.' + req.body).digest('hex');
  if (expected !== req.get('X-EG-Signature')) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  res.sendStatus(200);
});
import hmac, hashlib, json, os, time, requests

def eg(method, path, payload=None):
    body = json.dumps(payload, separators=(',', ':')) if payload else ''
    ts = str(int(time.time()))
    sk = os.environ['EG_SECRET_KEY']
    sig = hmac.new(sk.encode(), f'{ts}.{body}'.encode(), hashlib.sha512).hexdigest()
    headers = {
        'Content-Type': 'application/json', 'Accept': 'application/json',
        'X-EG-Public-Key': os.environ['EG_PUBLIC_KEY'],
        'Authorization': f'Bearer {sk}', 'X-EG-Timestamp': ts, 'X-EG-Signature': sig,
    }
    return requests.request(method, 'https://echogateway.ng/api/v1' + path, data=body or None, headers=headers).json()

init = eg('POST', '/transactions/initialize', {
    'amount': 45000, 'merchant_reference': 'INV-2026-000123',
    'customer': {'name': 'Amina Yusuf', 'email': 'amina@example.com'},
})
print(init['data']['checkout_url'])
PK="pk_live_xxx"; SK="sk_live_xxx"
BODY='{"amount":45000,"merchant_reference":"INV-2026-000123","customer":{"name":"Amina Yusuf","email":"amina@example.com"}}'
TS=$(date +%s)
SIG=$(printf '%s' "$TS.$BODY" | openssl dgst -sha512 -hmac "$SK" | sed 's/^.* //')

curl -X POST https://echogateway.ng/api/v1/transactions/initialize \
  -H "Content-Type: application/json" \
  -H "X-EG-Public-Key: $PK" -H "Authorization: Bearer $SK" \
  -H "X-EG-Timestamp: $TS" -H "X-EG-Signature: $SIG" \
  -d "$BODY"

# Test your credentials
TS=$(date +%s); SIG=$(printf '%s' "$TS." | openssl dgst -sha512 -hmac "$SK" | sed 's/^.* //')
curl https://echogateway.ng/api/v1/ping -H "X-EG-Public-Key: $PK" -H "Authorization: Bearer $SK" -H "X-EG-Timestamp: $TS" -H "X-EG-Signature: $SIG"

Go-live checklist

  • GET /ping returns your merchant name (keys and signature are correct).
  • Your server clock is accurate (NTP) — timestamps more than 5 minutes off are rejected.
  • merchant_reference is unique per order; retries reuse it.
  • Webhook endpoint verifies X-EG-Signature, is idempotent and returns 200 quickly.
  • You call /verify and check status and amount before giving value.
  • Return URL page does not give value by itself.
  • Secret key and webhook secret are stored server-side only (environment variables / vault).
  • Optional: add your server IPs to the allow-list under API & Webhooks.

Support: support@echosystems.ng · +2347053545602