The gap BKey fills for agent commerce
AI agents are starting to shop. They fill carts, compare prices, find coupons, and in some demos they “just buy it.” The way that almost always works today is that the agent is handed a payment method — a saved card, a Stripe customer ID, a session with the user’s card on file — and told to use it responsibly. The agent then either does, or doesn’t. That’s not consent. It’s trust-on-first-use, extended indefinitely, to a stochastic system. If the agent fabricates a purchase, gets prompt-injected by a product page, or silently upsells itself, the charge goes through and the user finds out at statement time. BKey flips the direction. The agent can initiate a checkout, but only a human with the phone and a biometric can authorize the charge. The card, the billing details, the saved payment method — none of that is handed to the agent. The agent’s capability is a proposal, not a payment.Who this is for
If your agent touches money, you want this:- Shopping agents — “find me a replacement air filter for my vacuum and order it”
- Travel agents — “book the flight closest to departure under $400”
- Grocery / recurring orders — “refill the dog food and add oat milk”
- B2B procurement — “renew the Notion seat and bill to ops@”
- Marketplace and ticket flows — “grab two tickets when the drop goes live”
The reference example
We ship a working agent checkout atexamples/typescript/agent-checkout — a Node CLI that builds a fixed cart and initiates a BKey-gated checkout end-to-end.
Acme Coffee — $69.00 — 2× Ethiopia Yirgacheffe, 1× Pour-over dripper. Approve with facial biometrics. The script prints the order confirmation JSON on stdout.
Logs go to stderr; the order confirmation is the only thing on stdout, so you can pipe the output straight into jq or back into the agent loop.
The minimal pattern
Insidesrc/index.ts, the gate is two calls plus a polling loop:
Design rules
Bind the approval to the exact transaction. Every field inCheckoutRequestInput ends up in front of the user’s eyes on their phone. merchantName, amount, currency, and lineItems are the consent contract — the user is approving this cart, not the agent’s next thought. Never mutate the cart after initiating the request; if the cart changes, create a new request.
Surface merchant identity, not agent identity. The push says “Acme Coffee wants $69.00,” not “Your AI assistant wants $69.00.” That’s deliberate — the user is deciding whether to pay the merchant, the agent is plumbing. merchantName and merchantDomain must be the real merchant. Spoofing them is the kind of thing that erodes trust in the whole flow.
Keep expiresInSecs tight. 300 seconds (5 minutes) is the default and the upper bound we recommend. Approvals are per-transaction; if the user walked away, the request should expire rather than sit open for an hour waiting for them to come back. Long expiries are an anti-pattern — treat a missed push as “no,” not “not yet.”
Use the checkout ID for replay protection. Each checkout is single-use. One approval finalizes exactly one order. If the agent needs to retry, create a new checkout with a new ID — don’t re-submit the same one. The BKey backend enforces this, but design your agent loop around it too: “approved” is a terminal state, not a reusable token.
Never expose BKEY_CLIENT_SECRET to the LLM. Agent client credentials live on the server side of your agent runtime. The LLM, the tool calls, the prompt — none of them should have access. Structurally this means your agent talks to your backend, and your backend talks to BKey. If the model can see the secret, so can a prompt injection.
Itemize — don’t roll line items into “subtotal”. lineItems is what lets the user catch a problem before spending money. “$69 to Acme Coffee” is the top-line. “2× Ethiopia Yirgacheffe at $18.50, 1× dripper at $32” is what lets the user notice the agent picked the wrong beans. Always send the itemization you built the cart from.
What the user sees
WhencreateCheckoutRequest fires, the BKey mobile app gets a push notification. Opening it shows:
- Merchant name and domain at the top — the entity being paid.
- Total amount and currency — what the charge will be.
- Itemized line items — title, quantity, unit price.
- Expiry countdown — how long they have to decide.
completed with the orderConfirmation populated.
Beyond the example
The reference example uses a fixed cart and a placeholder merchant. A real deployment wires in:- A real agent. Replace the hardcoded
lineItemswith whatever your agent assembled — catalog search, price comparison, a chat loop, a procurement workflow. - A real merchant integration.
checkoutUrlis where BKey finalizes the order. For BKey-native merchants (see the Shopify and WooCommerce demo plugins in this repo) the flow is automatic. For custom merchants, implement the callback on your own checkout endpoint and populateorderConfirmationwith order number, tracking, etc. - Multi-user. The example hardcodes one
BKEY_USER_DID. For an agent serving many users, resolve the DID from the authenticated agent session (the user who asked for the purchase) and pair new users with the device authorization flow. - Audit. Log
checkoutId,userDid,amount,lineItems,approvedAt,completedAt,orderConfirmation. This is the whole point — you now have a signed trail of every agent-initiated purchase.
References
@bkey/sdkon npm —createCheckoutRequest/getCheckoutRequestStatussource of truth- Reference example source
- MCP auth gate guide — the sibling pattern for gating MCP tool calls on biometric approval
- CIBA flow explainer — the underlying push-to-phone protocol