Stripe and Issuing
Many agent payments go through Stripe: PaymentIntents for online payments, and Issuing cards that an agent spends from. This guide records both. HalvorAI observes Stripe’s webhooks after the fact and does not touch the payment.
How it works
Section titled “How it works”Stripe objects do not know which agent created them. So you do two things:
- Tag the object when you create it, so it carries the agent, owner and instruction.
- Forward each Stripe webhook event to the SDK, which maps it to an APE event.
1. Tag what you create
Section titled “1. Tag what you create”Inside agent context, tagPaymentIntent writes metadata.halvor_* fields from the current context:
import Stripe from 'stripe';import { withAgentContext } from '@halvorai/ingest';import { tagPaymentIntent, tagIssuingCard } from '@halvorai/ingest/stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
await withAgentContext( { agent: { id: 'agt_procure_03' }, owner: { id: 'own_acme_ltd', type: 'business' }, instruction: { id: 'task_552', text: 'Reorder printer paper' }, }, async () => { const intent = await stripe.paymentIntents.create( tagPaymentIntent({ amount: 230000, currency: 'usd', description: 'Printer paper' }), ); },);For Issuing, create one card per agent and tag the card. Payments made with it are attributed to that agent automatically:
await stripe.issuing.cards.create( tagIssuingCard({ cardholder: cardholderId, currency: 'usd', type: 'virtual' }, { agentId: 'agt_travel_12' }),);The helpers add halvor_agent_id, halvor_owner_id, halvor_owner_type and halvor_instruction_id to the object’s metadata
and leave your other metadata alone. Pass an overrides object as the second argument to set them explicitly.
2. Forward webhooks
Section titled “2. Forward webhooks”In your existing webhook route, after your own signature check, hand the event to the SDK:
import { HalvorClient } from '@halvorai/ingest';import { stripeWebhookHandler } from '@halvorai/ingest/stripe';
const halvor = new HalvorClient();const forward = stripeWebhookHandler(halvor);
app.post('/stripe/webhook', async (req, res) => { const event = stripe.webhooks.constructEvent(req.rawBody, req.headers['stripe-signature']!, endpointSecret); forward(event); // maps and queues; events it does not handle are ignored res.sendStatus(200);});forward returns the new event id, or nothing for an event type it ignores. It never throws, so it cannot break your webhook
handler. You can also call halvor.stripe(event) directly.
Use the Stripe event as it arrives. Retries from Stripe carry the same event id, so a redelivered webhook produces a duplicate,
not a second record.
What gets mapped
Section titled “What gets mapped”| Stripe event | APE event |
|---|---|
payment_intent.amount_capturable_updated | payment.authorized |
payment_intent.succeeded | payment.settled |
payment_intent.payment_failed | payment.failed, with failure_reason from last_payment_error.code |
charge.refunded | payment.refunded |
charge.dispute.created | payment.disputed |
issuing_authorization.created, issuing_authorization.updated | payment.authorized on the card rail |
issuing_transaction.created | payment.settled, or payment.refunded for a refund |
The mapping reads:
- The amount and upper-cased currency, as minor units.
- The
PaymentIntentid aspayment.payment_id. descriptionaspayment.description.- The Connect destination as
payee.id. - For Issuing, the merchant name, category code, country and network id as the payee.
- A Shared Payment Token, when used, as
instrument.type: stripe_spt.
The Stripe object itself is attached as stripe.object evidence. Card numbers are redacted before sending.
Events without a tag
Section titled “Events without a tag”If a PaymentIntent has no halvor_agent_id, the event is still recorded, under the agent unattributed. It lowers your
attribution coverage but is never lost. Fix it by tagging at creation.
Check it works
Section titled “Check it works”- Trigger a test payment in Stripe’s test mode, or run
stripe trigger payment_intent.succeeded. - Open Payments in the sandbox dashboard. The event appears with its agent and payee.
- For Issuing, make a test authorization and look for
payment.authorizedfollowed bypayment.settled.