Skip to content

Documentation navigation

Stripe triggers

Handle payment, invoice and subscription events with private signing and local testing.

#Start an agent from Stripe events

Connect your own Stripe account's payment, invoice, or subscription events to a running agent. This connection receives selected snapshot events. It does not create charges, manage your mAIvn subscription, or need your Stripe API key.

#Create your connection

For local account testing, install the open-source Stripe CLI and sign in with stripe login. Obtain its webhook signing secret privately with stripe listen --print-secret. For a Live connection, use stripe listen --live --print-secret instead. Stripe documents that this secret stays the same across listener restarts. Keep it out of source files, shell history, and shared logs. Stripe listener reference.

In Studio, open Config → Triggers → Connections → Add a connection. In the Developer Portal, open your project's Integrations → Connections. Choose Stripe events, enter a recognizable name and that signing secret, and select Test. Test is the default; choose Live only for a live endpoint. Save the connection ID and callback address. The secret is stored privately and is not returned by the connection list or reveal.

Without Studio or the Portal, use the existing signed-in connection API with this body at POST /v1/projects/{project_id}/connections:

JSON
{
  "display_name": "Stripe sandbox",
  "setup_kind": "stripe_webhook",
  "webhook_secret": "<supplied privately>",
  "stripe_livemode": false
}

The mode is required. A signed live event sent to a Test connection is rejected, and vice versa. Connection management retains its existing human-session and CSRF requirements; your agent's project API key is a different credential.

#Declare what your agent handles

Add a declaration beside your configured Agent:

Python
paid = (
    agent.on_payment("conn_your_stripe", status="succeeded")
    .key("payment-received")
    .named("Payment received")
    .target("your-agent-id", version=1)
    .asking(
        "Summarize this payment event without taking external actions: "
        "{{event.payload.id}} for {{event.payload.data.object.id}}, "
        "amount {{event.payload.data.object.amount_received}} "
        "{{event.payload.data.object.currency}}."
    )
    .display_in_app()
)
DeclarationEvent
agent.on_payment(connection_id, status="succeeded")payment_intent.succeeded
agent.on_payment(connection_id, status="failed")payment_intent.payment_failed
agent.on_invoice(connection_id, event="paid")invoice.paid
agent.on_invoice(connection_id, event="payment_failed")invoice.payment_failed
agent.on_subscription(connection_id, event="created")customer.subscription.created
agent.on_subscription(connection_id, event="updated")customer.subscription.updated
agent.on_subscription(connection_id, event="deleted")customer.subscription.deleted
agent.on_subscription(connection_id, event="trial_will_end")customer.subscription.trial_will_end

The defaults are payment succeeded, invoice paid, and subscription created. Each helper selects exactly one event type. These are the eight supported snapshot types; thin events and other Stripe types are not admitted by this connection. A subscription update and a paid invoice mean different things. Do not use a checkout completion event as a substitute for payment confirmation.

In Studio, select the project, start the agent, and turn on the declared trigger. In a terminal, configure the project key and plane origins, then run:

Shell
maivn serve ./your_agent.py --project-id PROJECT --agent-id your-agent-id --version 1

After Announced, register with paid.invoke() once from a separate script. Keep registration out of import-time code and leave the serving process open. The Portal manages the connection and shows runs; the local agent still runs on your machine.

#Test with your Stripe account locally

Forward only your selected snapshot events to the saved callback:

Shell
stripe listen --events payment_intent.succeeded --forward-to http://127.0.0.1:8000/v1/connections/conn_your_stripe/events

For an explicitly configured Live connection, use stripe listen --live --events payment_intent.succeeded --forward-to <saved-callback> with its Live signing secret. The listener mode and saved connection mode must match.

Keep the listener running. In another terminal, stripe trigger payment_intent.succeeded creates a Stripe test fixture. Inspect the receiver response and then the trigger's run history and actual answer. The local listener needs no public tunnel or paid forwarding subscription. Stripe's local testing instructions.

For a Workbench endpoint, use that endpoint's own signing secret, select snapshot events, and update its destination to the saved connection's public callback. The CLI listener secret and Workbench endpoint secret are different. When the platform is local, the free receiver-only HTTPS tunnel can supply its public address. Do not mix test/live endpoints or secrets.

#Test the receiver without a Stripe account

Create a separate Test connection using a private synthetic signing secret. From tooling/mock-gateway in the source checkout:

Shell
uv run --frozen maivn-mock-gateway replay-stripe --connection-id conn_your_stripe --event-type invoice.paid --event-id evt_fixture_001 --repeat 2

The command prompts privately for the secret. Automation can supply one stdin line or name a private environment variable using --secret-env NAME; there is no secret argument. This command signs local synthetic snapshots and makes no payment or Stripe API call. Agent inference can still incur normal model usage.

Two sends with the same event ID produce one logical delivery. Use --new-event-id for a new event, --tamper to prove byte verification, or --mode live only with a separate synthetic Live connection. 202 proves durable reception, not completion of the agent's work.

#Payloads and recovery

Your prompt receives event.payload.id, type, created, api_version, livemode, optional account ID, and data.object. The object contains selected IDs, status, amounts, currency, relevant timestamps and bounded metadata. Expanded customer/subscription references become IDs. Client secrets, payment method details, addresses, authentication context and raw headers are excluded. Only put metadata you intend your agent to receive in Stripe metadata fields.

The receiver checks exact signed bytes and accepts any valid current v1 signature. Its replay window is five minutes. Retries with fresh signatures retain the same signed event ID; unsigned headers cannot choose another identity. Durably acknowledged events remain queued through a local service restart.

Events can arrive out of order. Distinct IDs remain distinct even when their object or creation time matches. Use your own business idempotency checks before performing side effects; this connection does not reconcile a customer's current subscription state for you.

ObservationCheck next
404 from the receiverActive connection, exact path, matching endpoint secret, unmodified bytes, and local clock.
400 after signature verificationMatching Test/Live mode, supported snapshot event type, and complete payload.
202, no runEnabled trigger, exact event type, project, and running agent ID/version.
CLI works but Workbench failsUse the Workbench endpoint secret and public callback, not the CLI listener secret.
Replayed event has no new runExpected for the same signed event ID; choose a new ID for a new fixture.

Stop the listener with Ctrl-C. Pause the trigger to stop new runs, or revoke the connection to reject new deliveries while preserving its record and history.