Payment rails: Aeropay & Aerosync
Our rail by default, yours if you have one. How the Aeropay pay-by-bank rail and its Aerosync bank-linking widget fit into round-ups.
The rail model
Round-ups accrue in SideBet regardless of rail. The rail only decides two things: how a user’s bank gets linked, and which processor pulls the batched transfer when their threshold is reached. Every merchant runs in one of two modes.
The default. ACH Direct Debit with Financial Connections for instant bank linking. Nothing to configure; SideBet holds the processor relationship.
You already have a processor contract, or you operate in a category Stripe restricts. Your credentials are stored encrypted on your merchant record and every call runs under your account.
Why sportsbooks pick Aeropay
Stripe treats sportsbooks and iGaming as a restricted category for ACH. Aeropay is licensed for iGaming, offers guaranteed funds on pending transfers, and its users can reuse a bank linked at any other Aeropay merchant. The trade-off is one or two extra screens the first time a user links, described below.
Aeropay
Aeropay is a pay-by-bank network. A user is identified by mobile number, holds an Aeropay account that spans every merchant on the network, and links one or more bank accounts to it. Merchants debit those accounts through Aeropay’s API. SideBet uses it for the single batched round-up transfer, never per purchase.
Set up the rail
Get Aeropay credentials
From your Aeropay dashboard: numeric merchant ID, API key and API secret. Sandbox and production are separate.
Configure the merchant in the SideBet admin
Merchants → Payment Providers → Aeropay. Enter the three values and pick the environment. Credentials are encrypted at rest.
Register the webhook
Admin → Merchants → Webhook URL gives you a signed URL. Add it in Aeropay for transaction_completed, transaction_declined and transaction_voided.
Go live
Flip the environment to production. The SDK loads Aeropay's real Aerosync widget automatically. Nothing changes in your integration.
What changes for the user
Aeropay needs a legal name and mobile number to create the user, and it sends a one-time code that must be confirmed before any bank can be linked. The Link SDK handles both. Pass name and phone in the link token and the details screen is skipped, but the code step always applies to a new Aeropay user.
The code is not always a text message
Aeropay chooses the channel and tells you which it used. On our sandbox merchant it emails the code, so the SDK says “Enter the code we emailed you” and names the inbox. Do not hard-code SMS wording.
| Screen | When | Link session kind |
|---|---|---|
| A little about you | Name or phone missing from the link token | collect_details |
| Enter the code we sent you | Always, for a new Aeropay user. The session carries mfaChannel, email or sms | confirm_mfa |
| Select your bank | Always, once | aerosync_widget |
Aerosync
Aerosync is Aeropay’s bank-linking widget, the equivalent of Plaid Link or Stripe Financial Connections. The user searches for their institution, signs in with their bank credentials inside the widget, and picks an account. Credentials go to the bank; neither SideBet nor your servers see them. Aerosync returns a connectionId, which SideBet exchanges with Aeropay for a verified bank account.
In production the SDK loads the widget from Aeropay’s CDN and mounts it in your page. In sandbox mode SideBet serves a built-in replica so you can test the whole flow without Aeropay credentials.
Two sandboxes
Aeropay’s own sandbox is the real thing: real tokens, real Aerosync at sandbox.aerosync.com. It needs credentials from Aeropay. Alongside it we run a built-in replica so you can build the whole flow before those arrive. Which one a merchant uses is decided by its credentials, so both run against the same deployment.
| Parameter | Type | Description |
|---|---|---|
000000 | Aeropay sandbox code | The one-time code Aeropay accepts in its sandbox. Other values return AP111. |
Any institution | replica bank picker | The replica lists twelve banks. All accept the same test login; the one you pick is the name that appears on the linked account. |
sync / no_mfa | replica username / password | Links straight through to the account list. |
sync / mfa | replica username / password | Adds a security question. The answer is "correct". |
123456 | replica code | The code the replica accepts. A number ending in 0000 makes it use the SMS channel instead of email. |
wrong | replica password | Shows the "Invalid credentials" state. |
The link flow, end to end
The Link SDK drives this for you. The endpoints are documented so you can build your own UI or debug a session; all of them take the link token as a bearer credential and run on your origin.
/api/link/bank-linkStart a bank link. Returns the next thing the user has to do.
// Aeropay needs name and phone first
{ "session": { "kind": "collect_details", "requiredFields": ["lastName", "phone"] },
"rail": { "railMode": "BYO", "pspType": "AEROPAY" } }
// That phone already has an Aeropay account
{ "session": { "kind": "confirm_mfa" }, "rail": { "railMode": "BYO", "pspType": "AEROPAY" } }
// Ready to link: open the Aerosync widget with this token
{ "session": { "kind": "aerosync_widget", "widgetUrl": "…", "token": "wtok_…", "environment": "sandbox" },
"rail": { "railMode": "BYO", "pspType": "AEROPAY" } }/api/link/consumerSupply the details Aeropay asked for, then call bank-link again.
{ "firstName": "Ada", "lastName": "Lovelace", "phone": "+14155550123" }/api/link/confirm-mfaSubmit the SMS code. On success, call bank-link again.
{ "code": "123456" }
// wrong code → 400
{ "error": "Aeropay POST /v2/confirmUser failed: Invalid verification code (AP111)" }
// The SDK rewrites this as "That code didn't match. Check the last 6 digits we texted you and try again."/api/link/bank-link/completeExchange the Aerosync connectionId for a verified bank and record the debit authorization.
{ "payload": { "connectionId": "conn_…" },
"mandate": { "userAgent": "…", "acceptedAt": "2026-09-02T20:11:28Z" } }
// → 200
{ "profile": { "pspType": "AEROPAY", "status": "VERIFIED", "bankName": "Chase", "bankLast4": "4321",
"mandateAcceptedAt": "2026-09-02T20:11:28Z" },
"rail": { "railMode": "BYO", "pspType": "AEROPAY" } }Transfers and webhooks
When a user’s accrued round-ups reach their threshold, SideBet creates one Aeropay transaction for the total. Aeropay reports the outcome by webhook; SideBet verifies the signed URL token, updates the ledger, and forwards a roundup.settled or roundup.returned event to your webhook URL.
| Aeropay topic | Debit status | You receive |
|---|---|---|
transaction_completed | SETTLED | roundup.settled — wallet credited, accrual reset |
transaction_declined | FAILED | roundup.returned — accrual restored, retried at the next threshold |
transaction_voided | RETURNED | roundup.returned |
Sandbox controls
In sandbox the replica settles every transaction after five seconds. Force a different outcome to test your webhook handling:
curl -X POST https://api-production-610a.up.railway.app/mock/aeropay/control/transactions/$TXN_ID/declined
# outcomes: declined · voided · processedAeropay quirks the integration absorbs
- Some failures come back as HTTP 200 with an error code in the body (for example AP111 for a bad SMS code). SideBet normalises these to real errors.
- Users are keyed by phone. A number that exists on the network always needs the SMS step the first time a new merchant links it.
- The Aerosync widget mounts by element id, so the SDK hosts it in your document rather than inside its own shadow root.
Hedge, Inc. · Payments for the people who use them