Webhooks
Send legacy Always Bank account snapshots to Jupid.
Use this webhook to send account-list changes from an existing Always Bank embed integration to Jupid. Your backend sends the event; Jupid receives it. It does not send transaction-processing notifications back to your backend.
For new integrations that upload accounts and transactions into a shared business, use the HTTP API. This webhook uses the legacy account snapshot flow and does not select a business or create an API source.
Jupid supplies a webhook URL and HMAC secret for each enabled environment. Keep the secret on your backend and send requests from there.
Account changes
The supported event is accounts.changed. Send it when a user's legacy
Always Bank account list changes.
Use the same stable sub as the user's embed JWT. Complete a legacy embed
login before sending snapshots so Jupid has the partner-user mapping. The
snapshot applies to that user's legacy Always Bank accounts; business.id
is descriptive metadata and does not route data to a shared business.
POST https://backend.jupid.com/api/webhooks/alwaysbank
Content-Type: application/json
x-jupid-signature: <hex hmac>The example uses the production endpoint. For staging, use the webhook URL provided by Jupid.
Request body
Send the full current account list for this user, including accounts that have not changed:
{
"type": "accounts.changed",
"sub": "stable-partner-user-id",
"payload": {
"tier": "paid",
"business": {
"id": "business-123",
"name": "Acme LLC"
},
"accounts": [
{
"id": "account-123",
"name": "Operating Checking",
"number": "****1234",
"type": "checking",
"currency": "USD",
"current_balance": 12000.34,
"plaid_token": "<optional-partner-provided-plaid-token>",
"status": "active"
}
]
}
}This is the legacy account-list payload from Authentication, not the HTTP API's business payload.
| Field | Requirement |
|---|---|
type | Must be accounts.changed. |
sub | Nonempty stable partner user ID. |
payload.tier | Required: free or paid. This webhook does not update billing entitlement. |
payload.business | Optional. If present, both nonempty id and name are required. |
payload.accounts | Required array. An empty array marks all this user's legacy Always Bank accounts inactive. |
Account id, name, currency | Required; ID and name are nonempty strings, currency is a three-character string. |
Account number, type, plaid_token | Optional strings. |
Account current_balance | Optional number in currency units. Omission preserves an existing balance; a new account starts at zero. |
Account status | Optional: active or disabled. |
Accounts omitted from a later snapshot are marked inactive. Their transaction
history is retained. This is a full-list update, so do not send separate
partial lists for different businesses under the same sub.
An enabled account with a plaid_token can use Jupid's Plaid synchronization
flow. Omit the token only when the account should have no usable Plaid
connection: omission clears a previously supplied token. The webhook itself
does not accept transaction records.
Signatures
Set x-jupid-signature to a hex HMAC-SHA256 digest of the raw request body,
using the webhook secret supplied for this environment. This is separate from
the embed JWT signing secret.
import { createHmac } from "node:crypto"
const signature = createHmac("sha256", webhookSecret)
.update(rawRequestBody)
.digest("hex")Use the exact bytes sent over HTTP as rawRequestBody. Reformatting
JSON after signing changes the digest.
Responses
| Status | Meaning |
|---|---|
200 | { "received": true }: the event was submitted for background processing. |
401 | JSON error missing_signature or invalid_signature. Check the environment secret and signed bytes before retrying. |
5xx | Submission or server failure. Invalid JSON or a body that fails validation also currently produces a server error. |
The response does not confirm that accounts were updated or that Plaid
synchronization completed. A missing partner-user mapping is detected in the
background, after the webhook has already returned 200, and the snapshot is
skipped.
Retry network failures and temporary server failures with backoff. For persistent failures, check the body against the schema above and contact Jupid. Repeating a snapshot uses the same account identities; it does not provide delivery ordering or a completion receipt.
Timing
Send accounts.changed after account-list changes and keep the latest full
snapshot available for retries. If an event was sent before the first legacy
embed login, send the latest snapshot again after login succeeds.
There is no snapshot version or ordering guarantee. An older event processed
later can overwrite newer data or mark newer accounts inactive. Avoid
replaying superseded snapshots; a 200 response alone does not establish
processing order.