Goal
The SePay integration connects a workspace to SePay through OAuth, provisions webhooks without manual SePay UI setup, ingests transaction events, and writes normalized finance records into the platform.Flow
- A workspace admin starts the OAuth flow.
- The platform stores an opaque state token in a short-lived HttpOnly cookie.
- The OAuth callback exchanges the code, encrypts the SePay tokens, stores
them in
sepay_connections, and redirects the browser back to the workspace integrations surface with a compact success or error query flag. - The platform syncs SePay bank accounts into
sepay_wallet_links. - The platform provisions a SePay webhook endpoint and stores the hashed token
in
sepay_webhook_endpoints. - SePay sends transaction webhooks to
/api/v1/webhooks/sepay/[token]. - The webhook route validates auth, resolves the workspace and wallet,
deduplicates/retries safely through
sepay_webhook_events, classifies the transaction, and insertswallet_transactions.
Data Model
sepay_connections
Stores the workspace-level OAuth connection, encrypted access and refresh
tokens, scopes, expiry, and connection status.
sepay_wallet_links
Maps SePay bank-account metadata to workspace wallets so webhook deliveries can
resolve to the correct wallet without manual per-event routing.
sepay_webhook_endpoints
Stores the hashed endpoint token, optional linked wallet, SePay webhook id, and
active/rotation state for workspace webhook routing. The endpoint ws_id is
treated as immutable after insert so existing event rows cannot drift across
workspaces through later admin-backed updates.
sepay_webhook_events
Stores the raw webhook payload, dedupe keys, processing status, failure reason,
and the created wallet_transactions.id when processing succeeds.
Processing Rules
- Incoming
transferAmountvalues are normalized to a positive magnitude. - Direction is derived from
transferType, not from the sign oftransferAmount. transferType: "in"is written as a positive wallet transaction amount.transferType: "out"is written as a negative wallet transaction amount.- Webhook retries reopen
failedevents immediately. receivedevents become retriable once they are older than the stale-event TTL and still do not have acreated_transaction_id.processedandduplicaterows are treated as non-replayable duplicates.- Auto-classification failures must not block ingestion; the route falls back to an uncategorized category.
- SePay AI enrichers reserve fixed workspace-scoped AI credits before each classifier/tagger model call and release the reservation on failure, so webhook retries do not leak credits or block on long model latency.
Operational Notes
- Required runtime env vars are documented in Secrets & Configuration.
- The end-to-end validation flow lives in SePay Testing Guide.
- OAuth callback state is intentionally cookie-backed rather than a custom HMAC payload.
- SePay account sync follows cursor pagination and retries
429responses using SePay’sx-sepay-userapi-retry-afterheader before failing the workspace sync. - Webhook provisioning only accepts numeric SePay bank-account ids
(
bank_account_idor numericid) and best-effort deletes the remote webhook if local persistence of the resulting webhook id fails. - Fallback uncategorized transaction categories are created through a workspace/name/direction advisory-lock RPC so concurrent webhook workers reuse one canonical fallback category id.
- Eventless webhook dedupe uses a short
received_atwindow for proactive duplicate detection, but the post-insert unique-key recovery path can still reopen the older canonical row when SePay retries the same event later.