Connect an Account
Open Sail's Connect Account Widget with a hosted session, so your end user can connect a store, card, or HSA/FSA account.
Your end user connects an account through Sail's Connect Account Widget, a single-use URL that opens a Sail-rendered login screen. A hosted session is the API object behind both of Sail's widgets. Its flow value tells Sail which one to open. The connect_account and reconnect flows open the Connect Account Widget. The upload_receipt flow opens the Upload Receipt Widget. This guide shows how to open the widget, know when the connection is ready, and reconnect it when the user's credentials stop working.
Your end user enters their login only inside Sail's iframe, so their credentials never touch your servers. Sail stores them, and your backend keeps using your partner API key for every other call.
Requirements
Before you start, make sure you have:
- A Sail user created for your end user.
- An API key with the scope that matches the connection type:
expensesfor a store or card connection, orbenefit_accountfor an HSA/FSA connection. - A webhook endpoint configured to receive events.
Step 1: Choose the connection type
The connection_type you send decides which login screen your end user sees. See Account Connections for what each type produces.
connection_type | Your end user logs into | Used by |
|---|---|---|
store | A store account, such as Amazon or Walgreens | The Itemize Transaction API |
benefit_account | An HSA/FSA administrator, such as HealthEquity | The Connect HSA/FSA API |
card | A bank account or credit or debit card | The Connect HSA/FSA API, to bring in the card purchases a reimbursement pays back |
To skip the selection screen and open one login directly, you also pass an ID in options. Use merchant_id with store, from List Merchants, or administrator_id with benefit_account, from List HSA/FSA Administrators. If your end user already has a connection for that store or administrator, the request returns 409 conflict.
Step 2: Create the session
Call Create Hosted Session from your backend with the user's id, flow set to connect_account, and the connection_type from Step 1. The example below opens Walgreens directly.
curl -X POST https://live.savewithsail.com/api/v1/hosted-sessions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_abc123",
"flow": "connect_account",
"options": {
"connection_type": "store",
"merchant_id": "mer_walgreens"
}
}'{
"url": "https://<HOSTED_SESSION_URL>",
"expires_at": "2026-08-10T15:31:07Z",
"flow": "connect_account"
}Save the url and expires_at. The URL works once, and it stops working at expires_at.
Step 3: Open the widget for your end user
Open the url in an iframe inside your app, or redirect your end user to it. To show the widget as an overlay instead of a full page, set modal to true in options when you create the session.
When modal is false, you style the window around the widget yourself, and your app receives two postMessage events from it:
| Event | Sail sends it when | Payload |
|---|---|---|
sail:complete | The flow finishes or is closed | type, entryPoint, userId, timestamp |
sail:error | Setup fails before the flow appears, with errorCode set to connection_failed | type, entryPoint, timestamp, errorCode, message |
sail:complete doesn't say whether the connection worked, because its payload has no result field. Close the window and wait for the webhook in Step 4.
If the session expires before your end user finishes, create a new session and open the new url.
Step 4: Wait for the connection to be ready
After your end user logs in, Sail connects to the account and syncs its data. This can take minutes for a store, because Sail retrieves the order history. Results arrive by webhook, so there's nothing to poll.
Fetch the data once you receive connection.synced. Its flags (balances_updated, activity_updated, expenses_updated) tell you which kinds of data moved.
Check the
Sail-Signatureheader before you process any event. Delivery is at-least-once, so deduplicate on the eventid. See Receiving New Data for both.
What you fetch depends on the connection type. For a store or card connection, you read expenses. For an HSA/FSA connection, you read accounts and balances. Each product's quickstart shows that call:
Step 5: Reconnect when the login stops working
A connection stops syncing when your end user's credentials go stale, for example after a password change. To find out, check the connection's status with Get Connection. A status of user_input_required or error means your end user needs to log in again.
To let your end user log in again, create a session with flow set to reconnect and the existing connection_id in options. Sail keeps the same connection, so you don't create a new one.
curl -X POST https://live.savewithsail.com/api/v1/hosted-sessions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_abc123",
"flow": "reconnect",
"options": {
"connection_id": "conn_8e3a"
}
}'{
"url": "https://<HOSTED_SESSION_URL>",
"expires_at": "2026-08-12T10:05:00Z",
"flow": "reconnect"
}Two cases return an error instead of a URL. An external connection has no login to refresh, so it returns 409 connection_not_reconnectable. A connection that is still syncing returns 409 conflict, so wait for the sync to finish and try again.
Next steps
- To see the connection types and what each one produces, see Account Connections.
- To pull item-level expenses after a store connection, see the Get Item-Level Expenses quickstart.
- To read HSA/FSA balances after a benefit account connection, see the Read an HSA/FSA Balance quickstart.
- To let your end user upload an itemized receipt through the Upload Receipt Widget, see Upload a Receipt.
Updated 13 days ago