# 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](https://developers.intasend.com/guides/collections/overview/#the-invoice) |
| `send_money_event` | A payout batch changes state | [Batch status](https://developers.intasend.com/guides/send-money/overview/#3-check-status) |
| `reversal_event` | A chargeback is created or changes status | [Chargeback](https://developers.intasend.com/guides/chargebacks/) |
| `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`:

```json
{
  "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

1. **Verify the challenge.** Reject any request whose `challenge` doesn't match yours.
2. **Respond with `200` or `201`** quickly. Any other status counts as a failure.
3. **Be idempotent.** You may get more than one event per resource (one per state change). Key your handling on `invoice_id` / `tracking_id` and `state`.
4. **Confirm critical events.** Before you release goods, call [payment status](https://developers.intasend.com/guides/collections/overview/#check-payment-status).

```python
# 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.
