Webhooks
Webhooks send an HTTPS POST to your server whenever something changes, so you don't have to poll.
Set up
In the dashboard, go to Settings → Webhooks → New and fill in:
- Endpoint: an
https://URL on your server. - Challenge: a secret string that you choose.
- Events: the events to subscribe to.
Events
topic | Fires when | Payload |
|---|---|---|
collection_event | An invoice is created or changes state | Invoice |
send_money_event | A payout batch changes state | Batch status |
reversal_event | A chargeback is created or changes status | Chargeback |
wallet_transfer_event | An intra-wallet transfer or split completes | from_data, to_data, amount |
subscription_event | A subscription's status, cycles or failure reason changes | Subscription with payments[] |
Payload
The body is the resource itself, plus topic and your challenge:
{
"invoice_id": "ABC123",
"state": "COMPLETE",
"provider": "M-PESA",
"value": 100,
"net_amount": 97,
"currency": "KES",
"account": "254712345678",
"api_ref": "order-123",
"provider_ref": "QWE123RTY",
"failed_reason": null,
"failed_code": null,
"topic": "collection_event",
"challenge": "my-secret-challenge"
}
Handling webhooks
- Verify the challenge. Reject any request whose
challengedoesn't match yours. - Respond with
200or201quickly. Any other status counts as a failure. - Be idempotent. You may get more than one event per resource (one per state change). Key your handling on
invoice_id/tracking_idandstate. - Confirm critical events. Before you release goods, call payment status.
# Flask
@app.post("/intasend/webhook")
def intasend_webhook():
event = request.get_json()
if event.get("challenge") != os.environ["INTASEND_WEBHOOK_CHALLENGE"]:
return "", 401
if event["topic"] == "collection_event" and event["state"] == "COMPLETE":
fulfil_order(event["api_ref"], event["invoice_id"])
return "", 200
Failures and replay
Failed deliveries are recorded under Webhooks → Events in the dashboard, and you can replay them from there. If your endpoint fails repeatedly, IntaSend emails you a warning and then disables the endpoint. Re-enable it in the dashboard once it's fixed.