Docs/SideBet/Link SDK

Link SDK

The front-end your users see. One script, one modal, a verified bank and round-ups turned on.

SideBet Link is shaped like Plaid Link or Stripe’s bank-link flow. Your server mints a short-lived link token for one user with your merchant API key. Your page loads one script and opens a modal that explains round-ups, lets the user pick how much to save, links their bank on whichever payment rail your account uses, records the debit authorization, and hands back a result. Your merchant key never reaches the browser.

How the pieces fitbash
your server ──(merchant API key)──▶ POST /api/roundup/link-token ──▶ { linkToken, expiresAt }
     │
     └── page: <script src=".../sdk/sidebet.js">  SideBet.create({ linkToken, onSuccess }).open()
                     │
                     └──(link token only)──▶ /api/link/session · /bank-link · /bank-link/complete · /status

Integration in three steps

1

Mint a link token on your server

One per page load or user session. Tokens are signed, scoped to that user, and expire in 30 minutes.

2

Open Link in the browser

Load the script, call SideBet.create({ linkToken }).open(). The modal handles consent, choices, bank linking and errors.

3

Record purchases server-side

POST /api/roundup/initiate on every bet. Nothing is debited per purchase; accrued cents are collected in one transfer at the threshold.

1. Server: mint a link token

POST /api/roundup/link-tokenbash
curl -X POST https://api-production-610a.up.railway.app/api/roundup/link-token \
  -H "Authorization: Bearer $MERCHANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "consumerId": "user_123",
    "consumerEmail": "ada@example.com",
    "consumer": { "firstName": "Ada", "lastName": "Lovelace", "phone": "+14155550123" },
    "ttlSeconds": 1800
  }'

# → { "linkToken": "slt_…", "expiresAt": "2026-09-02T20:30:00Z" }
ParameterTypeDescription
consumerId
stringYour stable id for this user. Everything SideBet stores is keyed on it.
consumerEmail
stringUsed for receipts and, on the Stripe rail, the bank-link session.
consumer.firstName / lastName
stringPre-fills the details step. Required by Aeropay before a bank can be linked; if you omit them the modal asks.
consumer.phone
E.164 stringAeropay identifies users by mobile number. Pass it to skip the details step entirely.
ttlSeconds
numberToken lifetime. Default 1800.

2. Page: open Link

index.htmlhtml
<script src="https://api-production-610a.up.railway.app/sdk/sidebet.js"></script>
<script>
  const link = SideBet.create({
    linkToken,                        // from your server
    onSuccess(result) {               // bank verified, round-ups on
      console.log(result.bank, result.accrual, result.rules, result.wallet);
    },
    onExit(reason, err) {},           // 'closed' | 'error' | 'expired'
    onEvent(e) {},                    // open, view, settings_saved, bank_link_started,
                                      // bank_linked, success, exit, error
    theme: { accent: '#0A6E5C' },     // optional override of the brand preset
  });
  link.open();
</script>

ESM builds are served at /sdk/sidebet.esm.js. TypeScript definitions ship alongside.

3. Server: record purchases

POST /api/roundup/initiatebash
curl -X POST https://api-production-610a.up.railway.app/api/roundup/initiate \
  -H "Authorization: Bearer $MERCHANT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "consumerId": "user_123", "consumerEmail": "ada@example.com", "transactionCents": 437 }'

# → { "roundUpCents": 37, "accrual": { "accruedCents": 37, "thresholdCents": 500 }, "debit": null }

What the user sees

Copy, logo, accent color and every money choice come from your Link preset, configured in the SideBet admin and published as a version. The modal walks through these steps, skipping any that do not apply.

StepWhat happens
ConsentLogo lockup, headline, three bullets, the debit authorization in plain words, Agree and continue. Every first-time link passes through here, whatever entry point opened the modal.
ChooseOnly the controls your preset allows: transfer threshold chips, save-the-cents vs next-dollar, 1×–3× boost, destination. A live example line and a "≈ N bets to your next transfer" estimate update as they tap.
DetailsAeropay rail only. Legal name and mobile number, pre-filled from the link token when you passed them.
CodeAeropay rail only, and only when that phone already has an Aeropay account. Six-digit SMS code.
BankStripe Financial Connections or the Aerosync widget, depending on your rail. The user picks their bank and signs in; SideBet never sees credentials.
SuccessBank on file, Active or Paused pill, progress bar, current rule and last transfer. "Manage round-ups" reopens Choose.

Built to a Stripe Link quality bar

Every step is a real form: Enter submits, the first field is focused, Tab stays inside the dialog, Esc closes and returns focus. Inputs are labelled, errors are announced to screen readers, and every tap target is at least 44px. Under 480px the modal becomes a bottom sheet. Provider errors are rewritten for people, so a wrong SMS code says “That code didn’t match” rather than a vendor error code.

Progress widget

A small embeddable that shows “accrued / threshold” with a bar, refreshed on a timer. It renders in a shadow root so your CSS does not leak in and picks light or dark text from the container it sits in.

Widgetjavascript
const widget = SideBet.widget({
  linkToken,
  el: '#roundups',
  refreshMs: 15000,
  onManage: () => SideBet.settings({ linkToken }).open(),
});

// The figure counts up as bets land. When a transfer settles the bar fills green,
// the widget shows "$3.85 moved to your wallet", then settles to "Last transfer $3.85 · today".
// Confetti fires once, on the user's first transfer.

widget.refresh();   // call after your own events if you want it instant
widget.destroy();

SideBet.status({ linkToken }) returns the same data as JSON if you would rather draw your own.

Manage round-ups

Reopen the choices for an enrolled userjavascript
SideBet.settings({ linkToken, onSuccess(r) { console.log(r.rules) } }).open();

// Unlinked users are routed to consent first — settings() can never skip the authorization.
// Server-side equivalent: PUT /api/roundup/consumer/:id/settings
//   { thresholdCents, strategy, multiplier, destination, paused }
// You receive roundup.settings_changed on your webhook URL when a choice changes.

States the modal handles

SituationWhat the user sees
New consumerConsent → Choose → bank picker → "Round-ups are on"
Bank already verifiedStraight to the success screen with current progress
Aeropay: name or phone unknown"A little about you" form, then the link continues
Aeropay: phone already has an accountSMS code screen; on a miss the field clears and refocuses
Microdeposit verification pending (Stripe)"Almost there" — round-ups keep tracking, the transfer waits
User cancels the bank widgetBack to consent
Round-ups paused"Round-ups paused" with an orange pill and a Resume action
Token expired"Session expired" → onExit('expired') — mint a new token

Security model

  • Link tokens are HMAC-signed, 30-minute, scoped to one merchant and one consumer. Nothing is stored server-side.
  • /api/link/* is CORS-open on purpose: it runs on your origin, the token is the credential, and it can only act on its own consumer. Rate-limited per IP.
  • The browser only ever receives publishable keys. Bank credentials go to Stripe or Aerosync directly.
  • All dynamic strings are HTML-escaped and the UI renders in a shadow root.

Events

ParameterTypeDescription
open
eventModal mounted.
view
{ step }A step rendered: consent, choose, details, mfa, bank, pending, success, error.
settings_saved
{ settings }The user changed threshold, rule, boost, destination or pause.
bank_link_started
eventHanded off to Stripe or Aerosync.
bank_linked
{ status }A bank came back. VERIFIED or PENDING_VERIFICATION.
success
eventUser pressed Done on the success screen. onSuccess fires with the result.
exit
{ reason }closed, error or expired.
error
{ message }Something failed; the modal shows a retry.

Hedge, Inc. · Payments for the people who use them