Sail

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 202 and a job_id while 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.

ResourceEvents
Connectionsconnection.created, connection.status_changed, connection.synced, connection.deleted
Accountsaccount.created
Expensesexpense.created, expense.updated, transactions.ingested
Receiptsreceipt.uploaded, receipt.processed, receipt.failed
Connect Account Widgethosted_session.expired
Usersuser.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