Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions api-reference/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ If none of the three is set, nothing is sent. That's not an error, it's silent b
"payer": null,
"payer_type": "human",
"network": "stripe",
"testnet": false,
"livemode": true,
"overpaid_by_cents": null,
"metadata": { "orderId": "order-123" }
}
Expand All @@ -57,7 +57,7 @@ If none of the three is set, nothing is sent. That's not an error, it's silent b
<ResponseField name="data.payer" type="string | null">On-chain payer address. `null` for card/bank rails.</ResponseField>
<ResponseField name="data.payer_type" type="string">`human` or `agent`.</ResponseField>
<ResponseField name="data.network" type="string">CAIP-2 network ID for on-chain rails, or `stripe` for card/bank.</ResponseField>
<ResponseField name="data.testnet" type="boolean">`true` on a test-mode checkout.</ResponseField>
<ResponseField name="data.livemode" type="boolean">`true` in live mode, `false` in test mode.</ResponseField>
<ResponseField name="data.overpaid_by_cents" type="number | null">Set when a bank-transfer buyer sent more than the amount due (beyond a 1-cent tolerance). `null` otherwise.</ResponseField>
<ResponseField name="data.metadata" type="object">Whatever you set on the checkout (or its link), unchanged.</ResponseField>

Expand All @@ -76,7 +76,7 @@ If none of the three is set, nothing is sent. That's not an error, it's silent b
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"livemode": true,
"description": null
}
}
Expand All @@ -99,14 +99,14 @@ Identical shape to `send.completed`, with `tx_hash: null`.
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"livemode": true,
"description": null
}
}
```

<Note>
The raw payload above is the literal snake_case JSON body AgentaOS `POST`s to your `webhookUrl`, this is what you'll parse in any language other than the SDK. `agentaos.webhooks.verify()` (Node.js) parses it and returns a camelCased, typed object instead: `linkId`, `sessionId`, `txHash`, `payerType`, etc. `rail`, `vendor_reference`, `testnet`, and `overpaid_by_cents` are additive fields not yet reflected in the SDK's TypeScript types, they arrive on the wire regardless of language.
The raw payload above is the literal snake_case JSON body AgentaOS `POST`s to your `webhookUrl`, this is what you'll parse in any language other than the SDK. `agentaos.webhooks.verify()` (Node.js) parses it and returns a camelCased, typed object instead: `linkId`, `sessionId`, `txHash`, `payerType`, `livemode`, etc. `rail`, `vendor_reference`, and `overpaid_by_cents` arrive on the wire but are not yet reflected in the SDK's TypeScript types, they're present regardless of language.
</Note>

## Delivery
Expand Down
20 changes: 19 additions & 1 deletion sdk/pay-webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ A discriminated union on `type`. `switch`/narrow on it to get typed `data`.
<ResponseField name="id" type="string">
Unique event ID, `evt_<uuid>`. Present on every event, both on the wire and on the object `verify()` returns.
</ResponseField>
<ResponseField name="type" type="'checkout.session.completed' | 'send.completed' | 'send.failed'" />
<ResponseField name="type" type="'checkout.session.completed' | 'send.completed' | 'send.failed' | 'subscription.created' | 'subscription.renewed' | 'subscription.payment_failed' | 'subscription.updated' | 'subscription.canceled'" />

<Warning>
Inside every event's `data`, **`amount` is a string**, e.g. `"49.99"`, not the `number` you get back from `checkouts.create()` or `paymentLinks.create()`. Parse it before doing math. See the [money model](/sdk/pay-overview#the-money-model).
Expand All @@ -100,6 +100,7 @@ Fires when a checkout session's payment confirms.
<ResponseField name="payer" type="string">Payer wallet address.</ResponseField>
<ResponseField name="payerType" type="'human' | 'agent'" />
<ResponseField name="network" type="string">CAIP-2 network ID.</ResponseField>
<ResponseField name="livemode" type="boolean">`true` in live mode, `false` in test mode.</ResponseField>
<ResponseField name="metadata" type="object">Whatever `metadata` you passed at `checkouts.create()`.</ResponseField>

### `send.completed`
Expand All @@ -114,6 +115,7 @@ Fires when an outbound send (a payout or transfer you initiated) is broadcast su
<ResponseField name="token" type="string" />
<ResponseField name="chainId" type="number" />
<ResponseField name="network" type="string" />
<ResponseField name="livemode" type="boolean">`true` in live mode, `false` in test mode.</ResponseField>
<ResponseField name="description" type="string | null" />

### `send.failed`
Expand All @@ -128,8 +130,24 @@ Same shape as `send.completed`, but `txHash` is always `null` and no broadcast s
<ResponseField name="token" type="string" />
<ResponseField name="chainId" type="number" />
<ResponseField name="network" type="string" />
<ResponseField name="livemode" type="boolean">`true` in live mode, `false` in test mode.</ResponseField>
<ResponseField name="description" type="string | null" />

### `subscription.*`

All five `subscription.*` events carry the same `SubscriptionData`, delivered to your organization webhook endpoint.

<ResponseField name="id" type="string">The subscription ID.</ResponseField>
<ResponseField name="status" type="string">Stripe status, verbatim: `incomplete`, `active`, `trialing`, `past_due`, `unpaid`, or `canceled`.</ResponseField>
<ResponseField name="planName" type="string | null">The plan name, from the backing payment link.</ResponseField>
<ResponseField name="currency" type="string" />
<ResponseField name="amountMinor" type="number">Per-cycle price in integer minor units. `1000` means `€10.00`.</ResponseField>
<ResponseField name="currentPeriodEnd" type="string | null">ISO 8601 timestamp, or `null` before the first cycle is set.</ResponseField>
<ResponseField name="cancelAtPeriodEnd" type="boolean">`true` once a cancellation is scheduled for period end.</ResponseField>
<ResponseField name="customerEmail" type="string | null" />
<ResponseField name="customerName" type="string | null" />
<ResponseField name="livemode" type="boolean">`true` in live mode, `false` in test mode.</ResponseField>

<AccordionGroup>
<Accordion title="Don't fulfill on the success page redirect">
`successUrl` is best-effort, the customer might close their browser before the redirect lands. Fulfill orders from the webhook (server-to-server, reliable), not the redirect.
Expand Down
64 changes: 53 additions & 11 deletions webhooks/events.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: "Events"
description: "Every webhook event AgentaOS sends: full payload fields, types, and a JSON example for each."
---

AgentaOS sends three event types today. Every event shares the same envelope, an `id`, a `type`, and a `data` object whose shape depends on the type. See [Webhooks](/payments/webhooks) for how to register a URL and verify the signature before trusting any of this.
AgentaOS sends webhook events for checkouts, outbound sends, and subscriptions. Every event shares the same envelope, an `id`, a `type`, and a `data` object whose shape depends on the type. See [Webhooks](/payments/webhooks) for how to register a URL and verify the signature before trusting any of this.

<Note>
Every `amount` field below is a **string** (e.g. `"49.99"`), not a number. This differs from `amount` on SDK create calls (checkouts, payment links), which is a plain number in currency units. Parse it before doing arithmetic.
Expand All @@ -18,7 +18,7 @@ The field names below are the literal snake_case keys AgentaOS `POST`s to your `
</ResponseField>

<ResponseField name="type" type="string">
One of `checkout.session.completed`, `send.completed`, `send.failed`. Switch on this to decide how to parse `data`.
One of `checkout.session.completed`, `send.completed`, `send.failed`, or a `subscription.*` event. Switch on this to decide how to parse `data`.
</ResponseField>

<ResponseField name="data" type="object">
Expand Down Expand Up @@ -62,8 +62,8 @@ Fired when a checkout is paid, by card, wallet, or stablecoin. This is the event
<ResponseField name="network" type="string">
CAIP-2 network ID for on-chain rails, e.g. `"eip155:8453"` for Base, or `stripe` for card/bank.
</ResponseField>
<ResponseField name="testnet" type="boolean">
`true` on a test-mode checkout, `false` in live.
<ResponseField name="livemode" type="boolean">
`true` in live mode, `false` in test mode.
</ResponseField>
<ResponseField name="overpaid_by_cents" type="number | null">
Set when a bank-transfer buyer sent more than the amount due (beyond a 1-cent tolerance). `null` otherwise.
Expand Down Expand Up @@ -92,7 +92,7 @@ Fired when a checkout is paid, by card, wallet, or stablecoin. This is the event
"payer": null,
"payer_type": "human",
"network": "stripe",
"testnet": false,
"livemode": true,
"overpaid_by_cents": null,
"metadata": { "orderId": "order-123" }
}
Expand Down Expand Up @@ -128,8 +128,8 @@ Fired when an outbound stablecoin send you initiated confirms on-chain.
<ResponseField name="network" type="string">
CAIP-2 network ID, e.g. `"eip155:8453"`.
</ResponseField>
<ResponseField name="testnet" type="boolean">
`true` on a test-mode send, `false` in live.
<ResponseField name="livemode" type="boolean">
`true` in live mode, `false` in test mode.
</ResponseField>
<ResponseField name="description" type="string | null">
Optional description you attached to the send. `null` if none was set.
Expand All @@ -149,7 +149,7 @@ Fired when an outbound stablecoin send you initiated confirms on-chain.
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"livemode": true,
"description": "Payout to contractor"
}
}
Expand Down Expand Up @@ -184,8 +184,8 @@ Fired when an outbound send fails to broadcast, network error, insufficient bala
<ResponseField name="network" type="string">
CAIP-2 network ID, e.g. `"eip155:8453"`.
</ResponseField>
<ResponseField name="testnet" type="boolean">
`true` on a test-mode send, `false` in live.
<ResponseField name="livemode" type="boolean">
`true` in live mode, `false` in test mode.
</ResponseField>
<ResponseField name="description" type="string | null">
Optional description you attached to the send. `null` if none was set.
Expand All @@ -205,7 +205,7 @@ Fired when an outbound send fails to broadcast, network error, insufficient bala
"token": "USDC",
"chain_id": 8453,
"network": "eip155:8453",
"testnet": false,
"livemode": true,
"description": null
}
}
Expand All @@ -215,6 +215,48 @@ Fired when an outbound send fails to broadcast, network error, insufficient bala
`send.failed` means the transaction never landed on-chain. It's distinct from a payment dispute or a card decline, those don't go through this event, they affect your balance directly. See [Payouts](/payouts/overview).
</Warning>

## Subscription events

Subscription lifecycle events, delivered to your **organization** webhook endpoint. All five share the same `data` shape.

- **`subscription.created`** first payment succeeded; now active (or trialing).
- **`subscription.renewed`** a cycle was paid and renewed for another period.
- **`subscription.payment_failed`** a renewal payment failed; now past due.
- **`subscription.updated`** the subscription changed (commonly `cancel_at_period_end`).
- **`subscription.canceled`** terminal; no further charges.

<Expandable title="data fields" defaultOpen>
<ResponseField name="id" type="string">The subscription ID.</ResponseField>
<ResponseField name="status" type="string">Stripe status: `active`, `trialing`, `past_due`, `unpaid`, `canceled`, `incomplete`.</ResponseField>
<ResponseField name="plan_name" type="string | null">Plan name, from the backing payment link.</ResponseField>
<ResponseField name="currency" type="string">e.g. `"EUR"`.</ResponseField>
<ResponseField name="amount_minor" type="number">Per-cycle price in integer minor units. `1000` = `€10.00`.</ResponseField>
<ResponseField name="current_period_end" type="string | null">ISO 8601, or `null` before the first cycle.</ResponseField>
<ResponseField name="cancel_at_period_end" type="boolean">`true` if cancellation is scheduled for period end.</ResponseField>
<ResponseField name="customer_email" type="string | null" />
<ResponseField name="customer_name" type="string | null" />
<ResponseField name="livemode" type="boolean">`true` in live mode, `false` in test mode.</ResponseField>
</Expandable>

```json Example
{
"id": "evt_3f2a1b0c-9d8e-7f6a-5b4c-3d2e1f0a9b8c",
"type": "subscription.renewed",
"data": {
"id": "sub_6pC2lNB6joCRQIZ1aMrTpi",
"status": "active",
"plan_name": "Pro Monthly",
"currency": "EUR",
"amount_minor": 1000,
"current_period_end": "2026-09-19T12:00:00.000Z",
"cancel_at_period_end": false,
"customer_email": "customer@example.com",
"customer_name": "Alex Rivera",
"livemode": true
}
}
```

## Next steps

<CardGroup cols={2}>
Expand Down