# Webhook Events

Sail sends webhook events as data moves through your integration. Each event has an `event` field that identifies what happened, and a `data` field with the resource Ids you need to fetch the current state.

A webhook payload has the following shape:

```
{
  "id": "evt_7f3k9m",
  "event": "connection.synced",
  "created_at": "2026-08-02T14:31:07Z",
  "user_id": "usr_abc123",
  "data": {
    "connection_id": "conn_4d81",
    "job_id": "job_r8v2",
    "balances_updated": true,
    "activity_updated": false,
    "expenses_updated": true
  }
}
```

Expand a resource below to see the events it can send.

<Accordion title="Connections">
  Events that fire for all connection types (card, store, benefit_account).

  | Event                       | Fires when                                                                    | `data` fields                                                                         |
  | :-------------------------- | :---------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ |
  | `connection.created`        | A user completes a connect account flow                                       | `connection_id`, `type`                                                               |
  | `connection.status_changed` | Any status transition (for example, `aggregating` to `active`, or to `error`) | `connection_id`, `previous_status`, `status`, `error_code`                            |
  | `connection.synced`         | A sync completes with no status change                                        | `connection_id`, `job_id`, `balances_updated`, `activity_updated`, `expenses_updated` |
  | `connection.synced.initial` | Initial basic account info syncs for a benefit account connection             | `connection_id`, `account_id`                                                         |
  | `connection.synced.profile` | Account profile data syncs for a benefit account connection                   | `connection_id`, `account_id`                                                         |
  | `connection.synced.activity`| Account activity syncs for a benefit account connection                       | `connection_id`, `account_id`                                                         |
  | `connection.deleted`        | A connection is revoked                                                       | `connection_id`                                                                       |
</Accordion>

<Accordion title="Expenses">
  Events that fire when expenses have been fully classified.

  | Event               | Fires when                                          | `data` fields |
  | :------------------ | :-------------------------------------------------- | :------------ |
  | `expenses.verified` | A user's expenses have been fully 213d classified   | `user_id`     |
</Accordion>

<Accordion title="Transactions">
  Events that fire when you push your own transaction data through an external connection.

  | Event                   | Fires when                  | `data` fields                                                  |
  | :---------------------- | :-------------------------- | :------------------------------------------------------------- |
  | `transactions.ingested` | A batch finishes enrichment | `connection_id`, `job_id`, `created`, `updated` |
</Accordion>

<Accordion title="Receipts">
  Events that fire when receipts are uploaded through the API or a connect account widget.

  | Event               | Fires when                              | `data` fields               |
  | :------------------ | :-------------------------------------- | :-------------------------- |
  | `receipt.uploaded`  | A receipt upload landed                 | `expense_id`                |
  | `receipt.processed` | Itemization and classification finished | `expense_id`, `expense_ids` |
  | `receipt.failed`    | Receipt unreadable or rejected          | `expense_id`, `reason`      |
</Accordion>

<Accordion title="Reimbursements">
  Events that fire when a reimbursement changes status.

  | Event                          | Fires when                          | `data` fields                                                     |
  | :----------------------------- | :---------------------------------- | :---------------------------------------------------------------- |
  | `reimbursement.status_changed` | Any reimbursement status transition | `reimbursement_id`, `previous_status`, `status`, `failure_reason` |
</Accordion>

<Accordion title="Connect Account Widgets">
  | Event                    | Fires when                                 | `data` fields        |
  | :----------------------- | :----------------------------------------- | :------------------- |
  | `hosted_session.expired` | A session ended without the user finishing | `session_id`, `flow` |
</Accordion>

<Accordion title="Users">
  | Event          | Fires when                            | `data` fields               |
  | :------------- | :------------------------------------ | :-------------------------- |
  | `user.deleted` | Purge completed after deleting a user | `user_id` (in the envelope) |
</Accordion>

## Events by integration path

Not every integration needs every event. Here's what to listen for based on your use case.

| Integration path   | Events to listen for                                                                                         |
| :----------------- | :----------------------------------------------------------------------------------------------------------- |
| Expenses           | `connection.created`, `connection.status_changed`, `connection.synced`, `expenses.verified`                                                                              |
| Classification     | `transactions.ingested`, `expenses.verified`                                                                                                                             |
| Account connection | `connection.created`, `connection.status_changed`, `connection.synced`, `connection.synced.initial`, `connection.synced.profile`, `connection.synced.activity` |