Home/Docs

Webhooks

Instead of polling status endpoints, Link App POSTs to your server when a payment or payout finishes. This is the recommended approach for production.

Register your webhook URL with Chapa (via your account manager or dashboard).

Events

EventMeaning
payment.successCard was topped up
payment.failedPayment failed
payment.cancelledPayment was cancelled
payout.successPayout completed
payout.failedPayout failed

Payment vs payout payloads

Payment and payout webhooks use different field sets. Do not expect payment fields on payout events (and vice versa).

Payment webhooksPayout webhooks
Primary referencelink_app_referenceinitiator_reference or chapa_reference
Type fieldpayment_typepayout_type
Extra contextprocessor_reference, payment_methodmeta (destination details)
Not includedinitiator_reference, chapa_reference, payout_typelink_app_reference, processor_reference, payment_type, payment_method

Optional fields are omitted when empty — you will not receive null placeholders.

Field order

Payload keys are sent in a stable, logical order:

  1. event, status, mode
  2. Type (payment_type or payout_type), currency, amount
  3. References and context (customer, meta, …)
  4. created_at, updated_at last

Shared fields

FieldDescription
eventWhat happened (payment.success, payout.failed, …)
statussuccess, failed, or cancelled
modelive or test
currencye.g. ETB
amountAmount in the card currency (number)
service_feeFee on the transaction, when applicable (payments)
merchant_referenceYour order ID, if you sent one
customerPayer or cardholder (first_name, last_name, email, phone_number)
created_atISO 8601 timestamp
updated_atISO 8601 timestamp

Payment webhooks

FieldDescription
link_app_referenceSame as link_reference from the payment API
processor_referenceChapa payment reference, when available
payment_typee.g. link, donation, event, qr_code, verification
payment_methode.g. telebirr
{
  "event": "payment.success",
  "status": "success",
  "mode": "live",
  "currency": "ETB",
  "amount": 100,
  "merchant_reference": "ORDER-2026-001",
  "link_app_reference": "CARD-R79130925F",
  "processor_reference": "CHr7eQE07ZFB",
  "payment_type": "link",
  "payment_method": "telebirr",
  "customer": {
    "first_name": "Abebe",
    "last_name": "Bikila",
    "phone_number": "960724272"
  },
  "created_at": "2026-06-21T12:00:00.000000Z",
  "updated_at": "2026-06-21T12:00:00.000000Z"
}

Payout webhooks

FieldDescription
payout_typecard_to_card, bank, or b2b
initiator_referenceCard-to-card correlation ID — same value returned from POST /card/payouts
chapa_referenceBank or B2B correlation ID — same value returned from POST /card/payouts
metaDestination details only; never repeats top-level references or payout_type

Each payout webhook includes one primary reference:

payout_type values

payout_typeReference fieldmeta fields
card_to_cardinitiator_referencesource_card_number, destination_card_number
bankchapa_referencesource_card_number, bank_slug, account_number, account_name
b2bchapa_referencesource_card_number, merchant_id

Bank payouts are finalized asynchronously when Chapa sends a payout status update. B2B payouts are finalized in the same API response when Chapa returns success.

Card-to-card

{
  "event": "payout.success",
  "status": "success",
  "mode": "live",
  "payout_type": "card_to_card",
  "currency": "ETB",
  "amount": 100,
  "initiator_reference": "GCy79131114X",
  "customer": {
    "first_name": "Abebe",
    "last_name": "Bikila",
    "phone_number": "960724272"
  },
  "meta": {
    "source_card_number": "7878064900",
    "destination_card_number": "8787064900"
  },
  "created_at": "2026-06-21T12:00:00.000000Z",
  "updated_at": "2026-06-21T12:00:00.000000Z"
}

Bank withdrawal

{
  "event": "payout.success",
  "status": "success",
  "mode": "live",
  "payout_type": "bank",
  "currency": "ETB",
  "amount": 100,
  "chapa_reference": "CP17815960544446",
  "customer": {
    "first_name": "Abebe",
    "last_name": "Bikila",
    "phone_number": "960724272"
  },
  "meta": {
    "source_card_number": "5610764625",
    "bank_slug": "telebirr",
    "account_number": "0926760003",
    "account_name": "Abebe Bikila"
  },
  "created_at": "2026-06-21T12:00:00.000000Z",
  "updated_at": "2026-06-21T12:00:00.000000Z"
}

B2B merchant payout

{
  "event": "payout.success",
  "status": "success",
  "mode": "live",
  "payout_type": "b2b",
  "currency": "ETB",
  "amount": 100,
  "chapa_reference": "CP17815960544446",
  "customer": {
    "first_name": "Abebe",
    "last_name": "Bikila",
    "phone_number": "960724272"
  },
  "meta": {
    "source_card_number": "5610764625",
    "merchant_id": "MERCH-12345"
  },
  "created_at": "2026-06-21T12:00:00.000000Z",
  "updated_at": "2026-06-21T12:00:00.000000Z"
}

Verifying webhooks

Chapa signs every webhook. Each request includes a link-app-signature header. Verify it with the webhook secret Chapa gives you when you register your URL.

Always verify the signature before updating orders or balances on your side.

Verification steps:

  1. Read link-app-signature from the request header.
  2. Load your WEBHOOK_SECRET from secure storage.
  3. Compute HMAC-SHA256(WEBHOOK_SECRET, WEBHOOK_SECRET) (sign the secret with itself).
  4. Compare the result to the header value. If they match, the request is authentic.

Setup

  1. Create an HTTPS endpoint on your server (e.g. POST /webhooks/link-app).
  2. Register the URL with Chapa.
  3. Return 200 quickly — do heavy work in the background.
  4. Handle duplicate deliveries safely (retries can happen).
  5. Branch on event and, for payouts, on payout_type.

For dashboards and reconciliation, you can also fetch history with GET /card/payments and GET /card/payouts instead of relying only on webhooks.

Next steps