ripllo.abandoned_cart.recovered.v1

Fires when a previously-abandoned cart is recovered — the buyer came back and completed a checkout that originated from a reminded cart. The primary metric for "did the recovery email pay off".

When it fires

When POST /api/v1/abandoned-cart/recover sets recoveredAt on a reminder row for the first time. The endpoint is idempotent on (accountId, checkoutSessionId), so retried partner-platform webhooks don't re-emit.

If the partner platform calls /recover for a checkout that has no matching reminder (the cart was never abandoned, or never reminded), no event fires — the recovery endpoint responds with recovered: false and Ripllo is silent.

Payload

{
  "id": "evt_01HX...",
  "type": "ripllo.abandoned_cart.recovered.v1",
  "createdAt": "2026-05-13T10:43:22.187Z",
  "accountId": "acc_01HX...",
  "data": {
    "reminder": {
      "id": "acr_01HX...",
      "accountId": "acc_01HX...",
      "customerId": "cus_01HX...",
      "cartId": "cart_storlaunch_19823",
      "email": "alice@example.com",
      "valueAtSend": 250000,
      "currencyAtSend": "IDR",
      "discountCodeId": "dc_01HX...",
      "status": "delivered",
      "sentAt": "2026-05-13T06:42:00.000Z",
      "recoveredAt": "2026-05-13T10:43:22.140Z",
      "externalSource": "storlaunch",
      "externalRef": "cart_storlaunch_19823"
    },
    "checkoutSessionId": "cs_storlaunch_01HX...",
    "timeToRecoveryMs": 14482140
  }
}

The timeToRecoveryMs is a convenience: the milliseconds between sentAt and recoveredAt. Useful for histograms without doing date math in your handler.

Handler examples

// Node
if (event.type === 'ripllo.abandoned_cart.recovered.v1') {
  const { reminder, checkoutSessionId, timeToRecoveryMs } = event.data;
  analytics.track('cart_recovered', {
    customerId: reminder.customerId,
    valueIdr: reminder.valueAtSend,
    discountUsed: reminder.discountCodeId !== null,
    hoursToRecovery: Math.round(timeToRecoveryMs / 3_600_000),
    checkoutSessionId,
  });
}
# Python
if event['type'] == 'ripllo.abandoned_cart.recovered.v1':
    reminder = event['data']['reminder']
    analytics.track('cart_recovered',
        customer_id=reminder['customerId'],
        value_idr=reminder['valueAtSend'],
        discount_used=reminder['discountCodeId'] is not None,
        hours_to_recovery=round(event['data']['timeToRecoveryMs'] / 3_600_000),
    )
// Go
if event.Type == "ripllo.abandoned_cart.recovered.v1" {
    var data struct {
        Reminder            ripllo.AbandonedCartReminder `json:"reminder"`
        CheckoutSessionID   string                       `json:"checkoutSessionId"`
        TimeToRecoveryMs    int64                        `json:"timeToRecoveryMs"`
    }
    _ = json.Unmarshal(event.Data, &data)
    analytics.Track("cart_recovered", data.Reminder.CustomerID, data.Reminder.ValueAtSend, data.TimeToRecoveryMs)
}

What to do

  • Update recovery analytics. This event is the single source of truth for the recovery rate. Aggregate by discountCodeId !== null to compare bundled-discount vs no-discount recovery.
  • Optionally thank the customer. Sending a "thanks for coming back!" follow-up can deepen engagement, but tread carefully — too many emails will provoke an unsubscribe.
  • Compute true recovery value. Compare reminder.valueAtSend (cart size at abandonment) to the actual checkout total (from the partner platform's order data) — the buyer may have added or removed items between abandonment and recovery.
  • Stop any "second reminder" sequences. If you've layered automations on top of Ripllo's first reminder, this is the cancel signal.

Common pitfalls

  • Counting valueAtSend as recovered revenue. It's the cart-snapshot value at abandonment time, not the final checkout total. The two diverge if the buyer added or removed items. For revenue reporting, use the partner platform's order-completed data and cross-reference by checkoutSessionId.
  • Treating recovery as instant. The buyer might come back days later. timeToRecoveryMs can easily span hours or days; bucket appropriately when reporting.
  • Assuming the reminder caused the recovery. Attribution is "the buyer was reminded and then completed a checkout". Some of those buyers would have come back anyway. The honest measure is incremental recovery rate vs a holdout group, which Ripllo doesn't currently A/B-test for you — you'd need to design that on the partner-platform side.
  • Sending the recovery event back to the partner. This event is for you (the integration owner) to track. Don't bounce it back to the partner platform — the partner already knows the checkout completed.

Next