Skip to main content

Bridge Cards — Stripe Issuing split

What this covers / who it's for. A dated digest of Bridge's platform/cards/* docs (22 pages) for anyone scoping the tracked B5 cards capability (D118 Follow-ups — "new IssuesCards capability + a new cards module, largest item, own design"). Facts reflect apidocs.bridge.xyz as fetched on 2026-09-03; re-verify via the bridge-development skill's fetch protocol (llms.txt → .md page → cite + date) plus changelog/changelog.md before relying on anything below. Nothing in this page is built — B5 has no code, no adapter interface, no module.

Headline: two generations of docs coexist. As of April 2026, Bridge cards are issued through Stripe Issuing, not Bridge's own card rail: Bridge owns the customer, KYC, and onchain funds pull; Stripe owns the cardholder, the card, authorizations, and almost all webhooks. 14 of the 22 fetched pages carry a <Warning> banner — "part of the legacy Bridge Cards API … use the consumer issuing guide instead" — and llms.txt line 215 states the legacy card_accounts API is deprecated. Legacy pages, marked [LEGACY] below, are historical/reference only — never the integration target for new work — but remain the only documented source for a few mechanics (PIN, freeze-reason vocabulary, fee payload shape) the current pages leave silent; see the gaps section.

Page index

#TitleGenPath (under apidocs.bridge.xyz/)
1OverviewCurrentplatform/cards/overview/overview.md
2Consumer issuing (Stripe guide)Currentplatform/cards/overview/stripe-issuing.md
3Customer eligibility and KYCCurrentplatform/cards/overview/kyc.md
4Commercial issuingCurrentplatform/cards/overview/commercial-issuing.md
5Funding strategiesCurrentplatform/cards/overview/funding-strategies.md
6Noncustodial walletsCurrentplatform/cards/overview/noncustodial.md
7WebhooksCurrentplatform/cards/overview/webhooks.md
8Additional card featuresCurrentplatform/cards/features.md
9StatementsCurrentplatform/cards/additional/statements.md
10SandboxCurrentplatform/cards/sandbox/sandbox.md
11Create/test your first card[LEGACY]platform/cards/overview/guide.md
12Terminology[LEGACY]platform/cards/overview/terminology.md
13Advanced money movement flows[LEGACY]platform/cards/overview/advanced.md
14Testing webhooks in sandbox[LEGACY]platform/cards/sandbox/testing.md
15Freezing a card account[LEGACY]platform/cards/additional/freezing.md
16Managing PINs[LEGACY], no current equivalentplatform/cards/additional/pins.md
17Customizing card art[LEGACY]platform/cards/additional/customizing.md
18Marketing, UX and compliance[LEGACY]platform/cards/additional/marketing.md
19Mobile wallet provisioning[LEGACY]platform/cards/additional/mobile-wallet-provisioning.md
20Safely reveal card details[LEGACY]platform/cards/additional/safely-reveal-card-details.md
21Real-time Authorization[LEGACY]platform/cards/additional/real-time-auth.md
22Fees[LEGACY], payload shape still cited in current samplesplatform/cards/additional/fees.md

1. Programme eligibility + the cards endorsement

Two programme types: consumer (individual Customer → Stripe Cardholder, created by Bridge) and commercial (business Customer → Stripe Connected Account (stripe_account_id) via Stripe Connect — the integrator creates Cardholders itself, header Stripe-Account: acct_…). Bridge does customer onboarding + KYC + endorsement; Stripe does cardholders, cards, spend APIs, webhooks.

Eligibility is a Bridge Customer endorsement, not a separate resource: endorsements[] entry {name:"cards", status, requirements:{complete[], pending[], missing, issues[]}} — statuses incomplete, approved, revoked, complete. Requested via GET /v0/customers/{id}/kyc_link?endorsement=cards → short-lived Persona URL, or by including "cards" in endorsements at customer creation. Card-specific issues codes gate region/format (endorsement_not_available_in_customers_region, residence_address_invalid_*, phone_number_invalid_format — US = 10 digits, else E.164); requirements also use all_of/ any_of groups and fresh_* re-attestation fields.

24-hour validity (bank partner rule). Once approved, the endorsement is valid for 24 hours; no card created in that window → auto-revoked, Stripe Cardholder set inactive. Reactivation: re-request the KYC link → customer reconfirms → re-approved → Bridge automatically flips the Cardholder back to active — no separate reactivate call.

Implications for Stables. Needs a deadline job against the 24h window (queued off customer.updated, not a lazy cron), persisting stripe_cardholder_id/stripe_account_id on the tenant's provider map the moment the endorsement approves (§2) — on top of, not instead of, the D117 activity-declaration gate. Source: platform/cards/overview/kyc.md, .../commercial-issuing.md.

2. Cardholder / card creation split (ids to store)

Bridge owns the Customer, KYC, the cards endorsement, and onchain funds pulls. Stripe owns the Cardholder, the Card, all authorization/transaction objects, almost all webhooks, shipping, PCI-scoped reveal, disputes. Consumer: Bridge auto-creates the Stripe Cardholder on endorsement approval; the Cardholder API is read-only for Bridge-managed cardholders, status Bridge-driven. Commercial: integrator creates Cardholders itself. Card creation: POST https://api.stripe.com/v1/issuing/cards (cardholder=ich_…, currency=usd — always usd regardless of settlement asset, type=virtual| physical, status=active, crypto_wallet[chain|currency|type|address]; commercial adds Stripe-Account). Must be activated before spend. Legacy (do not build against): POST /v0/customers/{id}/card_accounts {client_reference_id, currency, chain, crypto_account: {type, address}} — one card account per customer, immutable currency+chain.

IDs to store: stripe_cardholder_id (ich_…) or stripe_account_id (acct_…) alongside the Bridge customer_id, plus the Stripe card id (ic_…) — the statements endpoint (§8) already keys on ich_…/ic_…, not a Bridge card-account id. Wallet-to-card cardinality is inconsistent across pages — see gaps.

Implications for Stables. A new kind of join for the provider seam: one rail customer (Bridge) fronting a second vendor's own instrument model (Stripe). Neutral Card/Cardholder DTOs need both ids, and any provider_customers-style map needs a Stripe-side column too — see §13 for why this also means a second webhook ingress. Source: platform/cards/overview/stripe-issuing.md, .../commercial-issuing.md.

3. Funding strategies

crypto_wallet[type]: standard = noncustodial — customer's own external wallet, requires an onchain approval to a Bridge-provided contract, funds pulled just-in-time (no pre-funding), excess returned, Bridge does not track the external balance (approval failures surface only at auth time); bridge_wallet = custodial — Bridge-managed wallet the integrator funds/manages via the Wallets API, excess returned there. Stripe Financial Account = Stripe-native, no Bridge involvement. Chains: solana, base, tempo, world_chain (+ Linea); currencies usdc, usdb, path_usd (testnet). Commercial shared wallet: cards may share a Bridge Wallet only if they belong to the same Stripe Account = the same business Customer (AML constraint); consumer sharing requires the same Stripe Cardholder = the same Bridge customer. No page documents deposit-address provisioning for a current-generation card.

Implications for Stables. Custodial (bridge_wallet) fits our tenant-treasury shape best — one Bridge Wallet per business shared across its cards, reconciled like other Custody-group wallets. Noncustodial means auth success depends on externally-held chain state we don't control — an unfunded/under-approved wallet declines with no advance warning, a materially different failure mode than any other FiatRails/Custody capability; flag as a UX/support-load risk in any B5 design. Source: platform/cards/overview/funding-strategies.md, .../noncustodial.md.

4. Authorizations + real-time auth

Current lifecycle: issuing_authorization.created (Bridge validates approval + funds minus pending, submits onchain pull; may proactively over-pull by MCC/overcapture bound, refunding the difference post-settlement) → issuing_authorization.updated (incremental auths = extra pull per request_history item; refunds/reversals async onchain, small refunds may be batched; expired returns remaining funds) → issuing_transaction.created (capture; extra pull if capture > auth) → issuing_transaction.updated (no onchain effect). Onchain confirmation is async — a Stripe event can precede it. Real-time decisioning is off by default (issuing_authorization.request); consumer unsupported (contact Bridge), commercial supported with a hard 2-second SLA — Bridge may still reject after the integrator's approval (balance, onchain screening, velocity), so the authoritative outcome is always the later created/updated event.

Legacy real-time auth (no current equivalent): POST /v0/developer/cards/ auth_webhook_settings {enabled, webhook_url}; signed X-Webhook-Signature: t=<ms>,v0=<base64 RSA> over "{timestamp}.{raw_body}", reject >10min old. Default timeout 500ms, default fallback DECLINE — missing the window auto-declines regardless of the answer; even approved:true can be declined by timeout.

Implications for Stables. Any Stables-side authorization policy must be sub-second and idempotent on authorization_id, and treated as advisory — reconcile against the later Stripe/Bridge outcome event, never trust the real-time response as final. Stricter latency budget than any other bus-gated money-movement path in the app today. Source: platform/cards/overview/webhooks.md, .../real-time-auth.md.

5. Transactions, posted transactions, withdrawals

Current: transactions are Stripe issuing_transaction.*; Bridge defers to Stripe for the schema. The Stripe auth object carries crypto_transactions[] {type, crypto_transaction_confirmed: {amount, amount_mcc_upcharged, chain, confirmed_at, currency, fees[], from_address, to_address, transaction_hash, memo}, crypto_transaction_failed} — the only documented bridge between the fiat-shaped Stripe object and the onchain movement.

Legacy card_transaction fires twice per authorization: created (no crypto details), then updated seconds later with crypto_details{chain, amount, tx_hash, currency, tx_reference}. Posted transactions (posted_card_account_transaction) fire on settlement legs — no page documents its payload. Withdrawals: the only trace is the legacy webhook category card_withdrawal; no page describes withdrawal semantics, endpoints, or payloads at all — a custodial card's balance lives in the Bridge Wallet, not on the card.

Implications for Stables. A cards ledger must tolerate auth→settle amount drift (over/under- capture, FX), Stripe events arriving before onchain confirmation, batched refunds with no 1:1 authorization mapping, and settlement events landing on a frozen card (§6) — documented normal behavior, not edge cases. Source: platform/cards/overview/webhooks.md, .../advanced.md [LEGACY].

6. Freezes + reasons

Current: POST /v1/issuing/cards/{ic} status=inactive (freeze) / active (unfreeze) / canceled&cancellation_reason=lost (permanent), or via the Stripe Dashboard. No freeze-reason vocabulary documented on the Stripe path beyond cancellation_reason. Legacy: POST /v0/customers/{c}/card_accounts/{a}/freeze {initiator, reason:"lost_or_stolen"} / .../unfreeze {initiator}; freezes[] entries {reason, reason_detail, initiator, starting_at, ending_at, created_at} — only lost_or_stolen documented, full enum unstated.

Frozen cards still receive settlements, refunds, and forced transactions — a freeze stops new authorizations, not money movement already in flight, on both the legacy and current paths.

Implications for Stables. Freeze is per-card (a Stripe object update), not per-account — align the domain command with existing account.freeze semantics but expect different granularity; keep our own reason ledger since the vendor vocabulary is thin. Source: platform/cards/features.md, .../freezing.md [LEGACY].

7. PIN

Legacy only — no current-generation PIN page exists. Single op ("Update Card PIN", set/change, never view): POST /customers/{id}/card_accounts/{id}/pin returns a signed, time-limited, single-use URL rendered in an iframe/webview; listen for window.postMessage status codes (check event.origin). Custom styling via a separate repo. PIN never transits the integrator's servers (PCI). Silent on token TTL, the actual status-code values, and any current-path equivalent.

Implications for Stables. If PIN management is in B5's scope, this is a hard vendor question first (Open questions) — no documented current-generation flow to build against. Source: platform/cards/additional/pins.md [LEGACY].

8. Statements

Mandatory monthly delivery to cardholders is a stated programme obligation. On-demand generation: POST https://api.bridge.xyz/v0/cardholders/{ich_…}/cards/{ic_…}/statements/ {YYYY-MM}.pdf (Api-Key, accept: application/pdf) → PDF, viewable in the Bridge Dashboard, not Stripe's. Fees appear as a "Transaction Fees" summary row + per-row column (§11); custom templates go Bridge-shares-base → integrator-modifies → Bridge-approves. No list/history endpoint is documented — one statement per request, nothing to page through.

Implications for Stables. Bridge does not push or schedule statements — the integrator must schedule the monthly call, deliver it (email/portal), and probably cache the PDF. Delivery/ scheduling work on our side, not a webhook-driven flow. Source: platform/cards/additional/statements.md.

9. Mobile wallet provisioning

Current: Stripe Issuing push provisioning — Apple Pay/Google Pay entitlements arranged directly with Stripe, not documented further on Bridge's side. Legacy: POST /card_accounts/{id}/ create_mobile_wallet_provisioning_request (never called from mobile directly); Apple exchanges a cert-chain payload for {apple_pay:{activation_data, encrypted_pass_data, ephemeral_public_key}} to build a PKAddPaymentPassRequest (production-cards-only testing via TestFlight); Google's payload shape is undocumented.

Implications for Stables. Mostly "integrate with Stripe's own push-provisioning docs" — the cards corpus adds nothing current beyond the KYC/customer layer already in §1–§2. Source: platform/cards/additional/mobile-wallet-provisioning.md [LEGACY].

10. Designs

Current: standard physical designs ship "in as little as 2 days"; custom artwork via the Bridge account manager + Stripe's physical-cards docs. Legacy spec (still the only detailed one): mobile wallet art 1536×969 PNG, integrator + Visa logo, a forbidden-elements list (emboss, transparency, magstripe, chip, hologram, rounded corners, 3D); virtual card art 500×315 JPG/PNG, limited fonts. All artwork needs Bridge/Visa/bank approval.

Implications for Stables. Treat the legacy spec as the de-facto artwork constraint set until Bridge confirms otherwise for the current path. Source: platform/cards/additional/customizing.md [LEGACY].

11. Fees + money-movement drift

Two fee types: transaction fee (bps and/or fixed, separate domestic/international rates) and FX premium (bps on the network rate) — both configured globally per developer by Bridge at onboarding, no per-customer override; must appear in cardholder terms. Charged at authorization, included in the wallet hold; fixed fee charged once per transaction, never per incremental/completion. Over-capture adds the pct fee on the delta; under-capture refunds the pct delta but never the fixed fee; denied tx → no fee; auth expiration → always refunded. Payload: fees{transaction_fee{fee_amount, fee_config{percentage_fee_basis_points, fixed_fee_amount}}, fx_fee{...}, total_fee_amount} (negative = charged) — not confirmed whether/how this shape surfaces on current issuing_* objects. Payout: withheld per transaction into a Bridge-held ledger, paid monthly on the 5th to a configured external account.

Money-movement drift a ledger must model: MCC over-pulls refunded post-settlement, incremental auths as separate pulls, batched refunds with no 1:1 mapping, settlement events on frozen cards.

Implications for Stables. No existing mirror models a vendor-withheld, monthly-settled developer fee — a new ledger concept for B5, not an extension of the fee-free FiatRails/Conversion posture noted in provider-landscape §7(e). Source: platform/cards/additional/fees.md [LEGACY], .../webhooks.md.

12. Compliance, marketing, support, fraud

Marketing/UX/servicing guidelines exist but are off-doc — request the current version from the Bridge programme manager. Fees/terms must be disclosed at sign-up; commercial cardholders accept authorized-user (Lead Bank) terms with IP + date recorded. Cardholder phone support is a network requirement for consumer (optional-recommended commercial). Fraud liability sits with the platform developer (Stables) — docs recommend 3DS + Stripe's advanced fraud models; enriched merchant data is available for disputes.

Implications for Stables. The highest-stakes fact in this digest for a B5 design — it puts card fraud losses on Stables by default, distinct from the existing Fingerprint/SEON AssessesFraudRisk capability (D118 item 1), which Bridge doesn't serve and cards docs never reference; any B5 design needs an explicit call on whether/how existing fraud tooling extends to card auths. Source: platform/cards/additional/marketing.md [LEGACY], platform/cards/features.md.

13. Webhooks ↔ Stripe

Event familySourceVerificationNotes
customer.* (endorsement approved/revoked, KYC; populates stripe_cardholder_id/stripe_account_id)BridgeBridge webhook public keyThe only Bridge stream a current-gen integration needs
issuing_cardholder.createdStripeStripe signingFired when Bridge creates the cardholder (consumer)
issuing_authorization.created/updated, issuing_transaction.created/updatedStripeStripe signingDrives onchain pulls/returns; crypto_transactions[] on the auth object
issuing_authorization.requestStripeOff by default; commercial-only; 2s SLA (§4)
Legacy card_account, card_transaction, posted_card_account_transaction, card_withdrawalBridgeBridge RSALegacy programmes only
Legacy real-time authBridge → integratorX-Webhook-Signature t=,v0= RSA§4

This app already has a Bridge webhook ingressBridgeSignatureValidator + BridgeTenantResolver, registered per D118 item 5 (detailed in provider-landscape §5) — but it verifies only Bridge's own RSA scheme; it has no knowledge of Stripe's signing scheme, event shapes, or x-www-form-urlencoded wire format.

Stated plainly: a cards module needs a second, STRIPE-specific webhook ingress and its own tenant resolution — not just the existing Bridge validator. Stripe webhooks arrive independently of Bridge's, carry their own signature scheme, and resolve to a tenant via Stripe-Account/ cardholder id rather than Bridge's client_reference_id/provider_customers lookup — new plumbing under webhook-client-development's framework (a new SignatureValidator + TenantResolver pair, mirroring BridgeSignatureValidator's shape), not an extension of the Bridge source (D118 Follow-ups, B5). Tenant mapping must persist stripe_cardholder_id/stripe_account_id alongside the Bridge customer id (§2) for either ingress to resolve a tenant. Source: platform/cards/overview/webhooks.md.

Bridge doc inconsistencies / gaps

  • Deposit-address provisioning for a current-generation card — undocumented; the legacy card_account.funding_instructions{chain,address,currency} appears in a sandbox sample with no explanation.
  • card_withdrawal and posted_card_account_transaction payloads, and the full card_account.status and freeze reason enums — event categories/fields are named, none of these shapes/enums are fully documented.
  • PIN management on the current path and statement listing/history — neither has any current-generation page or endpoint at all.
  • Fee visibility on current issuing_* objects — only crypto_transactions[].fees[] visible and itself undocumented; whether the legacy fees{} shape (§11) carries over is unstated.
  • Wallet-to-card cardinality is inconsistent — funding-strategies says one wallet per card at a time; legacy noncustodial says one wallet ↔ one card account; sandbox describes sharing across multiple cards. Re-verify per programme type before designing around it.

Open questions for Bridge

Consolidated + tracked in flow-alignment.md §Open questions; this section keeps this page's own detail.

  • Is there a current-generation PIN management flow, or does the legacy iframe flow remain the only path? Same question for the complete card_account/Stripe-card status enum and freeze reason enum.
  • What is the deposit-address provisioning mechanism (if any) for a current-generation custodial or noncustodial card — does funding_instructions still apply, and to which programme type?
  • Do the legacy fees{} object and payout mechanics (§11) still apply under Stripe Issuing, or has fee reporting moved onto Stripe's own objects? What are the card_withdrawal and posted_card_account_transaction payload shapes?
  • What is the authoritative wallet-to-card cardinality per programme type, given the conflicting statements across the funding-strategies, noncustodial, and sandbox pages?
  • Does Bridge expect the integrator to run its own fraud scoring on card authorizations, or is there a Bridge/Stripe-side fraud signal a B5 design should consume instead of building new?

Fetched 2026-09-03

All 22 platform/cards/* pages listed in the Page index table above (§ top) were fetched raw as .md, HTTP 200, single attempt each, via https://apidocs.bridge.xyz/llms.txt. Each row's URL = https://apidocs.bridge.xyz/ + that row's Path, e.g. row 1 → https://apidocs.bridge.xyz/platform/cards/overview/overview.md. No unfetched or failed URLs (22/22).


← Bridge cluster index · ← Engineering wiki