Skip to navigation

Migrate to Unified Webhooks (intent.*)

Recurrente unified all payment events into a single format: intent.*. Instead of a separate event per payment method (payment_intent.*, bank_transfer_intent.*, etc.), you receive one intent.* event with a common structure, discriminated by the type field. You write one handler instead of one per type.

Existing accounts keep their contract. This guide is for accounts receiving both formats that want to migrate their integration. New accounts receive only the unified event when a replacement exists; they do not need to unsubscribe from the legacy duplicate.

To recognize payments stored before migration, match legacy_id against the old per-type ID. Once linked, id and receipt_number identify the same payment across both formats. Keep the event type too: a later status change is not a duplicate.

New events to configure

Subscribe your endpoint to these events from your Webhooks dashboard:

EventWhen it’s emitted
intent.succeededSuccessful charge (any payment method)
intent.pendingCharge started or pending (bank transfer, crypto)
intent.failedFailed charge
intent.canceledCanceled intent
intent.paidBalance payments only: delivered to the payer account

How it works

  1. For existing accounts, Recurrente still sends both formats for each supported payment: the legacy per-type one (payment_intent.*, etc.) and the unified one (intent.*), as separate deliveries.
  2. In your Webhooks dashboard, subscribe your endpoint to the intent.* events you care about.
  3. Your handler branches on the type field and processes.

If your account keeps both formats and your endpoint doesn’t filter by event type, it will receive both payment_intent.* and intent.* for the same payment. To avoid processing the same charge twice, subscribe to only one of the two formats (or check the event_type field in your handler).

A Sandbox lets you verify your intent.* handler without moving real money: a paid Sandbox checkout (card 4242 4242 4242 4242) fires intent.succeeded of type payment to that Sandbox’s endpoints. Sandboxes created before this change also receive the legacy payment_intent.succeeded.

The unified payload

{
"type": "payment", // payment | bank_transfer | crypto | balance | cash
"event_type": "intent.succeeded",
"id": "in_8c3a1f20",
"status": "succeeded", // pending | succeeded | failed | canceled | paid
"raw_status": "succeeded", // the concrete, non-normalized state
"amount_in_cents": 25000,
"currency": "GTQ",
"customer": { "id": "cus_...", "email": "...", "nit": "CF" },
"product": { "id": "prod_..." },
"checkout": { "...": "..." },
"payment": { "...": "..." },
"details": { "fee": 875, "installments": 1, "channel": "Tarjeta", "...": "..." }
}

The envelope and common core are identical for all five types; only type and the details contents change. See the full structure in the Webhooks documentation.

Mapping: legacy → unified

Legacy eventUnified eventtype
payment_intent.succeededintent.succeededpayment
payment_intent.failedintent.failedpayment
payment_intent.requires_capture / requires_verificationintent.pendingpayment
bank_transfer_intent.{pending,succeeded,failed}intent.{pending,succeeded,failed}bank_transfer
crypto_intent.{pending,succeeded,failed}intent.{pending,succeeded,failed}crypto
balance_intent.succeededintent.succeededbalance
balance_intent.paidintent.paidbalance
cash_intent.{succeeded,failed,canceled}intent.{succeeded,failed,canceled}cash

Intermediate card states (requires_capture, requires_verification) are normalized to pending. The concrete state is always available in raw_status.

Migration steps

  1. Review the new payload. Look at the structure in the Webhooks documentation, or fetch a real payment with GET /api/intents/{id} (returns the same format as the webhook).
  2. Update your handler to consume intent.* and branch on type:
    switch (event.type) {
    case "payment": /* event.details.fee, .installments, ... */ break
    case "bank_transfer":
    case "crypto":
    case "balance":
    case "cash": break
    }
    // state is always in event.status: pending | succeeded | failed | canceled | paid
  3. Subscribe your endpoint to the intent.* events from the Webhooks dashboard.
  4. (Optional) Unsubscribe from the per-type events. Once your unified handler works, unsubscribe from payment_intent.*, bank_transfer_intent.*, etc., to stop receiving the duplicate format.

Events that don’t change

subscription.*, refund.*, dispute.*, automated_bank_transfer_intent.*, and setup_intent.* stay exactly the same — they’re not part of the unified Intent resource.