Developers · Webhooks

Webhooks

Receive real-time notifications when events happen in your account. Every payload is signed so you can verify authenticity.

Getting started

  1. 1Register an HTTPS endpoint in your dashboard.
  2. 2Verify the signature header on each incoming request.
  3. 3Respond with 2xx quickly; we retry failures with backoff.

Event types

EventDescription
transfer.createdA transfer was initiated.
transfer.completedA transfer settled successfully to the recipient.
transfer.failedA transfer failed and funds were reversed.
payout.completedA payout to a beneficiary settled.
payment.succeededA collection / checkout payment succeeded.
payment.refundedA payment was refunded.
wallet.creditedA wallet received funds.
wallet.debitedA wallet was debited.
kyc.updatedA customer KYC status changed.

Example payload

{
  "id": "evt_1a2b3c",
  "type": "transfer.completed",
  "created": 1730000000,
  "data": {
    "transferId": "trf_9x8y7z",
    "amount": 1500.00,
    "currency": "ETB",
    "status": "completed"
  }
}

Signature verification

Every delivery includes an Amnen-Signature header containing a timestamp and an HMAC-SHA256 signature of `${timestamp}.${rawBody}`, computed with your endpoint's signing secret. Verify it before trusting a payload.

import crypto from 'crypto';

function verify(rawBody, header, secret) {
  const [tsPart, sigPart] = header.split(',');
  const timestamp = tsPart.replace('t=', '');
  const signature = sigPart.replace('v1=', '');

  // 1. Replay protection: reject anything older than 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    throw new Error('Timestamp outside tolerance — possible replay');
  }

  // 2. Recompute the HMAC and compare in constant time.
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    throw new Error('Invalid signature');
  }
}

Timestamp & replay protection

The signed timestamp lets you reject stale or replayed requests — discard any event more than 5 minutes old. Events also carry a unique id; store processed ids and ignore duplicates so a redelivery is never processed twice (idempotent consumption).

Retries & delivery

  • Return a 2xx quickly (within 5s). Any non-2xx or timeout is treated as a failure.
  • Failed deliveries are retried with exponential backoff over ~24 hours.
  • Because each event has a stable id, retries are safe — dedupe on your side.
  • Endpoints that keep failing are automatically disabled and surfaced in your dashboard.