Receiving New Data
Sail never expects you to poll for the current status, every piece of new or changed data is signaled by a webhook instead. New data shows up in one of two ways, a periodic automatic refresh or a refresh you trigger yourself. This page covers both, plus what you need to handle on every delivery.
Every webhook delivery is a POST to your configured endpoint, carrying one JSON envelope. Setting up that endpoint is a manual process today, the same as getting an API key, you send Sail your endpoint URL, and Sail sends back your webhook secret.
{
"id": "evt_7f3k9m",
"event": "expense.created",
"created_at": "2026-08-02T14:31:07Z",
"user_id": "usr_abc123",
"data": { "expense_id": "exp_9f2c", "connection_id": "conn_4d81" }
}The data field carries ids only, never PII. Treat every event as “something changed, go look,” not as a state transfer, fetch the resource by id for its current state instead of trusting the payload as the full picture.
How new data shows up
Sail syncs a connection’s data in one of two ways:
- Periodic automatic refresh happens on a cadence configurable per customer.
- On-demand refresh happens when you trigger a sync yourself through Force Refresh, which returns
202and ajob_idwhile the sync runs asynchronously.
Either way, you find out it’s done through a connection.synced webhook, which names which kinds of data moved (balances_updated, activity_updated, expenses_updated) so you know what to go pull.
Events by resource
Full per-event payload shapes live in Webhook Events. The table below groups each event by the resource it applies to.
| Resource | Events |
|---|---|
| Connections | connection.created, connection.status_changed, connection.synced, connection.deleted |
| Accounts | account.created |
| Expenses | expense.created, expense.updated, transactions.ingested |
| Receipts | receipt.uploaded, receipt.processed, receipt.failed |
| Connect Account Widget | hosted_session.expired |
| Users | user.deleted |
What every delivery requires of you
Verify the Sail-Signature header (t=<unix_ts>,v1=<hex>, an HMAC-SHA256 of the timestamp and raw body using your webhook secret) before processing a delivery, and reject anything outside a 5-minute timestamp tolerance. Delivery is at-least-once with no ordering guarantee, so deduplicate on the event’s id rather than assuming each event arrives once or in order. A failed delivery retries with exponential backoff for up to 24 hours, after which Sail stops retrying.
Next steps
- To register a webhook endpoint, see Set Up Webhooks.
- To look up the full event catalog and payload shapes, see Webhook Events.