Cards for AI agents with Stripe Issuing
An agent with a card can pay any merchant that takes cards, not only x402 APIs. That is also why a card needs a policy: a prompt injection that talks an agent into a purchase works on a card number too. With Stripe Issuing real-time authorizations, Stripe asks Burnbound before every charge on the agent's card, and the agent's Burnbound policy answers: the same caps, allowed hosts, rules and history as its x402 payments. The money stays in your Stripe account, and Burnbound never holds a Stripe key, only the endpoint's signing secret, encrypted.
How a card payment is decided
- The agent pays with its Stripe Issuing card at a merchant.
- Stripe sends
issuing_authorization.requestto your organization's Burnbound URL. - Burnbound checks Stripe's signature with your endpoint's secret and reads the agent from the card's metadata (
burnbound_agent_id). - The agent's policy decides, as for any payment: method allowed, merchant in the allowed hosts, maximum per payment, daily cap, team budgets and your own rules. Amounts in EUR or GBP are converted to USD, rounding up.
- Burnbound answers
approved: trueorapproved: falsewithin Stripe's 2-second window, and the payment shows in Spend with the merchant, its category and the amount in both currencies. - Later Stripe events close it: a capture settles it, a reversal or an expired authorization releases the spend.
A card cannot wait for a human. If a payment would need an approval (it is at or above the agent's approval threshold), it is declined with approval_required. Set the threshold above what the card should pay on its own.
What you need
- A Stripe account with Issuing enabled. Start in test mode: test cards and test authorizations, no real money.
- An agent in Burnbound and an owner or admin of the organization to set up the connector.
- The agent's id (the agent's page in the dashboard shows it).
Step 1: Create the card connector in Burnbound
In the dashboard, open Organization → Cards (Stripe Issuing) and click Create the connector. Choose what Burnbound answers if a decision runs out of time (decline is the default and the recommended choice). The connector shows the webhook URL for Stripe, under the app's host:
https://app.burnbound.dev/v1/cards/stripe/<connector id>/webhook
Copy it. Until you paste the signing secret (step 4), the connector is pending: Burnbound answers 400 to everything that reaches the URL, and Stripe applies your Issuing default, so nothing is approved.
Step 2: Point Stripe's real-time authorizations at it
In Stripe, open the Issuing settings (dashboard.stripe.com/account/issuing), turn on real-time authorizations and set the URL from step 1 as the endpoint. Stripe sends that endpoint only issuing_authorization.request, and asks it before approving each authorization on your cards.
In the same settings, choose what Stripe does when it gets no answer within 2 seconds. Decline is the safe choice.
Step 3: Add the events endpoint
Stripe sends every other event to a regular webhook endpoint. Open Developers → Webhooks → Add endpoint, paste the same URL and select these three events:
issuing_authorization.createdissuing_authorization.updatedissuing_transaction.created
Save the endpoint. Burnbound uses these events to settle captures and release holds. Without them, an approved card payment stays counted until its hold expires, 31 days later.
Step 4: Paste the signing secrets
Each Stripe endpoint has its own signing secret (it starts with whsec_). In Burnbound, on the same connector, fill in:
- Real-time authorization signing secret (required): the secret of the endpoint in the Issuing settings.
- Events secret (optional): the secret of the Developers → Webhooks endpoint, when Stripe gives it a different one.
Save. The URL does not change. Burnbound stores both encrypted and only shows their last 4 characters. An event signed with either one is accepted; one signed with any other secret is refused.
Step 5: Tag each card with its agent
Each card names the agent that pays with it in its metadata: key burnbound_agent_id, value the agent's id. In Stripe: Issuing → Cards → the card → Metadata. With the Stripe CLI:
stripe issuing cards update ic_… -d "metadata[burnbound_agent_id]=<agent id>"
A card without that key, or naming an agent of another organization, is declined with card_not_linked.
Step 6: Allow the method and the merchants on the agent
On the agent's page in Burnbound:
- Under Payment methods, turn on Stripe Issuing · card. It is off by default, for every agent.
- Add the merchants to the allowed hosts. A card payment's host is the merchant's website when Stripe gives one (
openai.com), and otherwise its category:mcc-<category>.card, with dashes, for examplemcc-computer-software-stores.card. The website is whatever the merchant declares, so it is not proof of who the merchant is. To allow or block a specific merchant, write a rule on its payee (stripe-merchant:<network id>) or on its merchant category. - Check the maximum per payment and the daily cap: card payments count toward them in USD, like any other payment.
The merchant's category and country are also facts your own rules can use. See how to limit an AI agent's spending for the rest of the controls.
Step 7: Try it in test mode
Create a test authorization on the tagged card with Stripe's test helpers. Within the cap it is approved; over the maximum per payment it is declined:
stripe post /v1/test_helpers/issuing/authorizations \
-d card=ic_… -d amount=500 -d currency=eur \
-d "merchant_data[category]=computer_software_stores"
Capture it to see the payment settle in Burnbound:
stripe post /v1/test_helpers/issuing/authorizations/iauth_…/capture
stripe trigger issuing_authorization.request also works as a quick check of the URL and the secret: it creates a card of its own, without burnbound_agent_id, so Burnbound declines it with card_not_linked. Each decision is in Spend and in Audit (card.authorization.approved, card.authorization.declined, card.authorization.captured, card.authorization.released).
Currencies
- USD always works. EUR, GBP and the other currencies the European Central Bank publishes are converted with its daily rate (refreshed hourly, rounded up). A card currency without a rate (for example KWD, unless the server pins one in
FIAT_USD_RATES) is declined withcurrency_not_supported, before any rule. - Decimals follow Stripe, not ISO 4217: the authorization's
amountis in minor units of the card currency. 12.50 EUR is1250, 500 JPY is500, 1.500 KWD is1500(thousandths) and 5 ISK is500(Stripe counts ISK and UGX with two decimals). - Conversions round up to the micro-dollar, so a payment never looks smaller than it is. The caps compare the USD amount; the original amount and the rate used are kept with the decision and in Audit.
Limits in this version
- No approvals on cards. A payment that would need a human is declined (
approval_required). - Daily exchange rates. Other currencies use the European Central Bank's reference rate of the day, not the live market rate (see Currencies above).
- The spend is the authorized amount, plus any over-capture. A partial capture does not lower it. A capture above what was authorized, or of an authorization Burnbound did not approve, is still recorded as spend and flagged (
over_capture,unreserved_capture). A refund on the card is only audited. - One connector per organization. Creating another replaces it, with a new URL: update both endpoints in Stripe. A replaced or revoked connector declines new authorizations but still records captures and releases for 30 days.
- Out of time. Burnbound decides within 1.5 seconds. If it cannot, it answers what the connector says (decline by default) and audits
decision_timeout.
Burnbound itself is set up in the quickstart. The same policy covers the agent's Stripe refunds and its x402 payments to APIs from the catalog; every control is in How to limit an AI agent's spending.