diff --git a/content/docs/admin-api/guides/meta.json b/content/docs/admin-api/guides/meta.json
index 08916472..555ea5bd 100644
--- a/content/docs/admin-api/guides/meta.json
+++ b/content/docs/admin-api/guides/meta.json
@@ -4,6 +4,7 @@
"external-checkout",
"order-management",
"subscription-management",
+ "subscription-charge-webhooks",
"exports",
"testing-guide",
"iframe-payment-form",
diff --git a/content/docs/admin-api/guides/subscription-charge-webhooks.mdx b/content/docs/admin-api/guides/subscription-charge-webhooks.mdx
new file mode 100644
index 00000000..134173a4
--- /dev/null
+++ b/content/docs/admin-api/guides/subscription-charge-webhooks.mdx
@@ -0,0 +1,118 @@
+---
+title: Handle Subscription Charge Webhooks
+sidebar_label: Subscription Charge Webhooks
+sidebar_position: 2
+tags:
+ - Guide
+---
+import { Callout } from 'fumadocs-ui/components/callout';
+
+Use `transaction.created` webhooks to react to successful subscription charges, distinguish an initial charge from a renewal, and correlate the charge with a record in an external system.
+
+## Detect a Successful Charge
+
+A `transaction.created` event represents a payment attempt, not necessarily a successful payment. Process the event as a charge only when both of these conditions are true:
+
+- `data.type` is `debit`
+- `data.status` is `succeeded`
+
+This is more precise than using `order.created`, which indicates that an order was placed, or `order.updated`, which can fire repeatedly as related order data changes.
+
+```json title="Successful Subscription Charge"
+{
+ "event_id": "f7eb1338-0934-4cda-8128-d6a77761a368",
+ "event_type": "transaction.created",
+ "data": {
+ "id": 10416,
+ "type": "debit",
+ "status": "succeeded",
+ "amount": "29.99",
+ "order": "116663",
+ "subscription": {
+ "id": 12345,
+ "billing_cycle": 3
+ },
+ "attribution": {
+ "metadata": {
+ "plan_id": "plan_abc123"
+ }
+ }
+ }
+}
+```
+
+
+Webhook deliveries are retried when your endpoint does not return `200`. Store the `event_id` or transaction `data.id` before performing side effects so a repeated delivery does not trigger fulfillment or another external action twice.
+
+
+## Identify Initial and Renewal Charges
+
+For a subscription charge, `data.subscription` contains the subscription ID and its billing-cycle counter:
+
+- `billing_cycle: 0` — initial subscription charge
+- `billing_cycle: 1` — first renewal
+- `billing_cycle: 2` — second renewal
+- and so on
+
+A value greater than `0` identifies a renewal. The subscription block is empty for transactions that are not associated with a subscription.
+
+```javascript title="Filter and Classify a Subscription Charge"
+function handleTransactionCreated(payload) {
+ const transaction = payload.data;
+
+ if (transaction.type !== "debit" || transaction.status !== "succeeded") {
+ return;
+ }
+
+ if (!transaction.subscription?.id) {
+ return;
+ }
+
+ const isRenewal = transaction.subscription.billing_cycle > 0;
+ const externalPlanId = transaction.attribution?.metadata?.plan_id;
+
+ // Deduplicate payload.event_id or transaction.id before side effects.
+ // Process the initial or renewal charge in your external system.
+}
+```
+
+## Correlate Charges with an External System
+
+If an external identifier should accompany every charge for a subscription, store it in the subscription's attribution metadata. Renewal orders and their transactions include that attribution in webhook payloads, so the receiver does not need a follow-up API request.
+
+### 1. Define the Metadata Field
+
+Create an attribution metadata definition once per store. The field type for a string value is `text`.
+
+```json title="Define Attribution Metadata" http-method="POST" http-target="https://{store}.29next.store/api/admin/metadata/"
+{
+ "object": "attribution",
+ "key": "plan_id",
+ "name": "Plan ID",
+ "type": "text"
+}
+```
+
+See [metadataCreate](/docs/admin-api/reference/metadata/metadataCreate) for available field types and validation options.
+
+### 2. Set the Identifier on the Subscription
+
+Set the value when the subscription is created, or add it later with [subscriptionsPartialUpdate](/docs/admin-api/reference/subscriptions/subscriptionsPartialUpdate).
+
+```json title="Set Subscription Attribution Metadata" http-method="PATCH" http-target="https://{store}.29next.store/api/admin/subscriptions/{id}/"
+{
+ "attribution": {
+ "metadata": {
+ "plan_id": "plan_abc123"
+ }
+ }
+}
+```
+
+### 3. Read the Identifier from the Webhook
+
+Read the value from `data.attribution.metadata.plan_id` after confirming that the transaction succeeded. This pattern also works for other external identifiers, such as a CRM subscription ID or fulfillment-plan reference.
+
+
+Use customer metadata for values that belong to the customer regardless of subscription. Use order metadata for values that apply to one order only. Attribution metadata is the recommended surface for an identifier that should accompany each charge for a specific subscription.
+
diff --git a/content/docs/webhooks/index.mdx b/content/docs/webhooks/index.mdx
index b0ecbbd4..7ec1cacf 100644
--- a/content/docs/webhooks/index.mdx
+++ b/content/docs/webhooks/index.mdx
@@ -71,6 +71,10 @@ Returning a `410` response code indicates the target resource is no longer avail
| `ticket.created` | Triggers a new support ticket is created. | [View Example](/docs/webhooks/reference/support/ticket.created) |
| `ticket.updated` | Triggers when an existing support ticket is updated. | [View Example](/docs/webhooks/reference/support/ticket.updated) |
+
+To react to a successful subscription charge, use `transaction.created` and check both the transaction type and status. See [Handle Subscription Charge Webhooks](/docs/admin-api/guides/subscription-charge-webhooks) for renewal detection, external-ID correlation, and retry-safe processing.
+
+
### Webhook Data Structure
Webhook payloads follow the same structure as Admin API data serializers, which makes them predictable. In general, the data in a webhook payload matches the data you would get by retrieving the same resource through the API. You can set up test webhooks and view the webhook logs in the dashboard to help build and verify your receiver.