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 — "newIssuesCardscapability + a newcardsmodule, largest item, own design"). Facts reflectapidocs.bridge.xyzas fetched on 2026-09-03; re-verify via thebridge-developmentskill's fetch protocol (llms.txt →.mdpage → cite + date) pluschangelog/changelog.mdbefore 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
| # | Title | Gen | Path (under apidocs.bridge.xyz/) |
|---|---|---|---|
| 1 | Overview | Current | platform/cards/overview/overview.md |
| 2 | Consumer issuing (Stripe guide) | Current | platform/cards/overview/stripe-issuing.md |
| 3 | Customer eligibility and KYC | Current | platform/cards/overview/kyc.md |
| 4 | Commercial issuing | Current | platform/cards/overview/commercial-issuing.md |
| 5 | Funding strategies | Current | platform/cards/overview/funding-strategies.md |
| 6 | Noncustodial wallets | Current | platform/cards/overview/noncustodial.md |
| 7 | Webhooks | Current | platform/cards/overview/webhooks.md |
| 8 | Additional card features | Current | platform/cards/features.md |
| 9 | Statements | Current | platform/cards/additional/statements.md |
| 10 | Sandbox | Current | platform/cards/sandbox/sandbox.md |
| 11 | Create/test your first card | [LEGACY] | platform/cards/overview/guide.md |
| 12 | Terminology | [LEGACY] | platform/cards/overview/terminology.md |
| 13 | Advanced money movement flows | [LEGACY] | platform/cards/overview/advanced.md |
| 14 | Testing webhooks in sandbox | [LEGACY] | platform/cards/sandbox/testing.md |
| 15 | Freezing a card account | [LEGACY] | platform/cards/additional/freezing.md |
| 16 | Managing PINs | [LEGACY], no current equivalent | platform/cards/additional/pins.md |
| 17 | Customizing card art | [LEGACY] | platform/cards/additional/customizing.md |
| 18 | Marketing, UX and compliance | [LEGACY] | platform/cards/additional/marketing.md |
| 19 | Mobile wallet provisioning | [LEGACY] | platform/cards/additional/mobile-wallet-provisioning.md |
| 20 | Safely reveal card details | [LEGACY] | platform/cards/additional/safely-reveal-card-details.md |
| 21 | Real-time Authorization | [LEGACY] | platform/cards/additional/real-time-auth.md |
| 22 | Fees | [LEGACY], payload shape still cited in current samples | platform/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 family | Source | Verification | Notes |
|---|---|---|---|
customer.* (endorsement approved/revoked, KYC; populates stripe_cardholder_id/stripe_account_id) | Bridge | Bridge webhook public key | The only Bridge stream a current-gen integration needs |
issuing_cardholder.created | Stripe | Stripe signing | Fired when Bridge creates the cardholder (consumer) |
issuing_authorization.created/updated, issuing_transaction.created/updated | Stripe | Stripe signing | Drives onchain pulls/returns; crypto_transactions[] on the auth object |
issuing_authorization.request | Stripe | — | Off by default; commercial-only; 2s SLA (§4) |
Legacy card_account, card_transaction, posted_card_account_transaction, card_withdrawal | Bridge | Bridge RSA | Legacy programmes only |
| Legacy real-time auth | Bridge → integrator | X-Webhook-Signature t=,v0= RSA | §4 |
This app already has a Bridge webhook ingress — BridgeSignatureValidator +
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_withdrawalandposted_card_account_transactionpayloads, and the fullcard_account.statusand freezereasonenums — 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 — onlycrypto_transactions[].fees[]visible and itself undocumented; whether the legacyfees{}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 freezereasonenum. - What is the deposit-address provisioning mechanism (if any) for a current-generation custodial
or noncustodial card — does
funding_instructionsstill 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 thecard_withdrawalandposted_card_account_transactionpayload 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).