> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Recovery

> The payloads sent to your webhook endpoint when abandoned cart recovery or subscription dunning events occur.

## Abandoned Cart Recovery Events

The following webhook events track the abandoned cart recovery lifecycle:

| Event | Description |
| - | - |
| `abandoned_checkout.detected` | An abandoned checkout has been detected. Sent when a payment is identified as abandoned (failed or incomplete) and the recovery workflow begins. |
| `abandoned_checkout.recovered` | The customer completed payment through the recovery link. The `recovered_payment_id` field contains the successful payment ID. |

### Abandoned Checkout Payload Fields

<ParamField body="payment_id" type="string" required>
  The original payment that was abandoned. Use this to look up product, amount, and currency details.
</ParamField>

<ParamField body="brand_id" type="string" required>
  The brand this abandoned checkout belongs to.
</ParamField>

<ParamField body="customer_id" type="string" required>
  The customer who abandoned the checkout.
</ParamField>

<ParamField body="abandonment_reason" type="string" required>
  Why the checkout was abandoned. One of:

  * `payment_failed` — Customer attempted payment but it failed
  * `checkout_incomplete` — Customer visited checkout but never attempted payment
</ParamField>

<ParamField body="status" type="string" required>
  Current lifecycle state of this recovery attempt. One of:

  * `abandoned` — Detected, no emails sent yet
  * `recovering` — At least one recovery email sent
  * `recovered` — Customer completed payment
  * `exhausted` — All emails sent or newer checkout found
  * `opted_out` — Customer unsubscribed
</ParamField>

<ParamField body="abandoned_at" type="string" required>
  ISO 8601 timestamp of when the checkout was detected as abandoned.
</ParamField>

<ParamField body="recovered_payment_id" type="string | null">
  The payment ID of the successful recovery payment. `null` until the checkout is recovered.
</ParamField>

### Example: Handling ACR Webhooks

```javascript theme={null}
app.post('/webhooks/dodo', async (req, res) => {
  // Verify the signature first. See /developer-resources/webhooks#verifying-signatures.
  // With express.raw(), req.body is a Buffer, so parse it after verification.
  const event = JSON.parse(req.body);

  switch (event.type) {
    case 'abandoned_checkout.detected':
      console.log(`Checkout abandoned: ${event.data.payment_id}`);
      console.log(`Reason: ${event.data.abandonment_reason}`);
      // Track abandonment in your analytics
      await trackAbandonment(event.data);
      break;

    case 'abandoned_checkout.recovered':
      console.log(`Checkout recovered: ${event.data.payment_id}`);
      console.log(`Recovery payment: ${event.data.recovered_payment_id}`);
      // Grant access, update records
      await handleRecovery(event.data);
      break;
  }

  res.json({ received: true });
});
```

***

## Dunning Events

The following webhook events track the subscription dunning lifecycle:

| Event | Description |
| - | - |
| `dunning.started` | A dunning attempt has been created for a subscription that entered `past_due` or `on_hold`, or was cancelled by the customer. The `trigger_state` field says which. |
| `dunning.recovered` | The customer updated their payment method and the resulting payment succeeded. The `payment_id` field contains the successful payment ID. |

### Dunning Attempt Payload Fields

<ParamField body="subscription_id" type="string" required>
  The subscription that triggered the dunning attempt.
</ParamField>

<ParamField body="brand_id" type="string" required>
  The brand this dunning attempt belongs to.
</ParamField>

<ParamField body="customer_id" type="string" required>
  The customer who owns the subscription.
</ParamField>

<ParamField body="trigger_state" type="string" required>
  The subscription state that triggered dunning. One of:

  * `on_hold` — Renewals stopped due to payment failure
  * `past_due` — A renewal failed while a [grace period](/features/subscription#grace-period) is open
  * `cancelled` — Customer cancelled from the customer portal
</ParamField>

<ParamField body="status" type="string" required>
  Current lifecycle state of this dunning attempt. One of:

  * `recovering` — Dunning emails are being sent
  * `recovered` — Customer updated payment method and payment succeeded
  * `exhausted` — All emails sent or subscription state changed
</ParamField>

<ParamField body="created_at" type="string" required>
  ISO 8601 timestamp of when the dunning attempt was created.
</ParamField>

<ParamField body="payment_id" type="string | null">
  The payment ID of the successful recovery payment. `null` while recovering.
</ParamField>

### Example: Handling Dunning Webhooks

```javascript theme={null}
app.post('/webhooks/dodo', async (req, res) => {
  // Verify the signature first. See /developer-resources/webhooks#verifying-signatures.
  // With express.raw(), req.body is a Buffer, so parse it after verification.
  const event = JSON.parse(req.body);

  switch (event.type) {
    case 'dunning.started':
      console.log(`Dunning started for subscription: ${event.data.subscription_id}`);
      console.log(`Trigger: ${event.data.trigger_state}`);
      // Track dunning in your system
      await trackDunning(event.data);
      break;

    case 'dunning.recovered':
      console.log(`Subscription recovered: ${event.data.subscription_id}`);
      console.log(`Recovery payment: ${event.data.payment_id}`);
      // Reactivate access, update records
      await handleDunningRecovery(event.data);
      break;
  }

  res.json({ received: true });
});
```

<Tip>
  Subscribe to both `dunning.started` and `dunning.recovered` to track the full dunning lifecycle. Use `dunning.started` to pause grace periods or flag at-risk subscriptions in your system.
</Tip>

<CardGroup cols={2}>
  <Card title="Abandoned Cart Recovery" icon="cart-shopping" href="/features/recovery/abandoned-cart-recovery">
    Configure ACR email sequences and discount incentives.
  </Card>

  <Card title="Subscription Dunning" icon="rotate" href="/features/recovery/subscription-dunning">
    Configure dunning email sequences for lapsed subscriptions.
  </Card>

  <Card title="Subscription Webhooks" icon="repeat" href="/developer-resources/webhooks/intents/subscription">
    Related subscription lifecycle events like `subscription.on_hold` and `subscription.cancelled`.
  </Card>
</CardGroup>

## Webhook Payload Schema

The schema below is the abandoned checkout payload (`AbandonedCheckoutPayload`). For the dunning payload fields, see [Dunning Attempt Payload Fields](#dunning-attempt-payload-fields).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.