# Assinaturas — eventos

Eventos de webhook emitidos ao longo do ciclo de vida de uma assinatura. Payloads completos no contrato OpenAPI, seção `webhooks`: `/openapi.json`

## Base comum

Eventos `subscription.*`, `charge.created` e o `payment.success` de cobranças com `subscriptionId` compartilham estes campos:

| Campo | Tipo | Descrição |
|---|---|---|
| `event` | string | Nome do evento |
| `timestamp` | string | ISO 8601 |
| `accountNumber` | string | Conta do seller |
| `subscriptionId` | string | ID da assinatura |
| `customerId` | string | ID do cliente |
| `status` | string | Status da assinatura no momento do evento — **exceto em `charge.created`**, onde é o status da cobrança |
| `email` | string | E-mail do cliente |
| `taxId` | string | CPF ou CNPJ |
| `interval` | string | `MONTHLY`, `YEARLY`, `ONE_TIME`… |
| `currentCycleNumber` | number | Número do ciclo atual |
| `metadata` | object \| null | Metadados da assinatura. Vem `null` quando não há nenhum — não `{}` |
| `startDate` | string | ISO 8601 |
| `canceledAt` | string \| null | Data de cancelamento |
| `items` | array | Itens da assinatura |
| `currentCycle` | object \| null | Ciclo corrente |
| `customer` | object | Dados do cliente |
| `coupon` | object | **Opcional.** `{ "code": "..." }`, presente só quando a assinatura tem cupom |

`payment.failed` **não** segue esta base — tem schema próprio (veja abaixo).

### `items[]`

`price` e `product` só aparecem quando o item tem `priceId` associado. Itens de cobrança avulsa (`type: "ONE_TIME"`, criados fora de um produto) chegam sem esses dois blocos.

`items[].status` acompanha o momento do item: `PENDING`, `TRIALING`, `ACTIVE`, `CHARGED`, `CANCELED`.

### `currentCycle`

```json
{
  "cycleNumber": 3,
  "amount": 89.90,
  "status": "PAID",
  "chargeDate": "2026-08-10T14:45:05.953Z",
  "paidAt": "2026-08-10T13:02:46.710Z",
  "paymentMethod": "creditcard"
}
```

`paymentMethod` vem em **minúsculo** e com valores próprios do ciclo: `pix`, `creditcard`, `boleto`, `pix_automatico`. Não confunda com `paymentMethod` do `payment.success` de cobrança avulsa, que é `PIX` em maiúsculo.

## O que muda em cada evento

| Evento | Diferença em relação à base |
|---|---|
| `subscription.created` | `status: PENDING`, `currentCycle: null`, itens em `PENDING` |
| `subscription.trial` | `status: TRIALING`, `currentCycleNumber: 0`, `currentCycle: null`, itens em `TRIALING`. **Não existe objeto `trial`** — o prazo está em `items[].price.trialDays` |
| `subscription.activated` | `status: ACTIVE`, ciclo `PAID` |
| `subscription.renewed` | Igual a `activated`. Quando emitido pelo motor de cobrança, acrescenta `nextCycleChargeDate` e `nextCycleAmount` |
| `subscription.upgraded` | Igual a `activated`; muda só o `event` |
| `subscription.item_added` | Igual a `activated`; muda só o `event` |
| `subscription.item_removed` | Igual a `activated`; muda só o `event` |
| `subscription.downgrade_scheduled` | Igual a `activated`; muda só o `event` |
| `subscription.cancel_scheduled` | Segue `ACTIVE`; acrescenta `cancelAtPeriodEnd`, `cancelRequestedAt`, `effectiveAt`, `cancelReason` |
| `subscription.canceled` | `status: CANCELED`, `canceledAt`, `cancelReason`. **`currentCycle` continua preenchido**, com o status em que o ciclo ficou (`CANCELED` ou `PAID`) |
| `payment.success` | Base com ciclo pago, mais `chargeId` e `invoiceId` quando existirem |
| `charge.created` | Base mais os campos da cobrança; `status` passa a ser o da cobrança |
| `payment.failed` | Schema próprio, sem a base |

### `subscription.cancel_scheduled`

`effectiveAt` vem de `nextCycleChargeDate` da assinatura — a data em que o próximo ciclo seria cobrado. `cancelReason` é texto livre, o mesmo enviado no `reason` da requisição de cancelamento.

### `charge.created`

Além da base, traz os dados da cobrança gerada:

| Campo | Descrição |
|---|---|
| `chargeId` | ID da cobrança |
| `invoiceId` | Fatura vinculada, quando houver |
| `transactionId` | ID da transação |
| `amount` / `currency` | Valor e moeda (`BRL`) |
| `paymentType` | `PIX`, `BOLETO`, `CREDIT_CARD`, `PIX_AUTOMATICO` |
| `type` | Tipo da cobrança, ex.: `RECURRING`, `ONE_TIME_ITEM` |
| `status` | **Status da cobrança** (`PENDING`, `PROCESSING`…), não o da assinatura |
| `emvQrCode` | Copia-e-cola do Pix, quando `paymentType: PIX` |
| `pixCollectionType` | `COB` ou `COBV` |
| `dueDate` | Vencimento |
| `recurrencyId` | Recorrência do Pix Automático, quando houver |
| `metadata` | Metadados da cobrança |

O `status` é a pegadinha mais comum: filtrar `charge.created` por `status === "ACTIVE"` não retorna nada, porque o valor é o da cobrança recém-criada.

### `payment.failed`

Schema próprio. Usa **`accountId`**, não `accountNumber`, e não carrega assinatura, itens nem ciclo:

```json
{
  "event": "payment.failed",
  "timestamp": "2026-06-20T00:09:17.334Z",
  "accountId": "4231833",
  "customerId": null,
  "name": "João da Silva",
  "email": "joao@example.com",
  "taxId": "11144477735",
  "cellphone": null,
  "item": null,
  "metadata": null,
  "failed": "Pagamento não autorizado"
}
```

`failed` é uma **string** com o motivo, não um objeto. `customerId`, `cellphone` e `item` costumam vir `null`.

## Exemplo — subscription.created

```json
{
  "event": "subscription.created",
  "timestamp": "2026-08-14T22:31:59.869Z",
  "accountNumber": "4231833",
  "subscriptionId": "sub_1786746719522_a8h3r7tix",
  "customerId": "cus_1786746719497_9px4bs9dh",
  "status": "PENDING",
  "email": "lucas@example.com",
  "taxId": "12354358903",
  "interval": "MONTHLY",
  "currentCycleNumber": 1,
  "metadata": {
    "plan": "PREMIUM",
    "checkoutSessionId": "pa_mstiv3jy_b012bc"
  },
  "startDate": "2026-08-14T22:31:59.522Z",
  "canceledAt": null,
  "currentCycle": null,
  "coupon": { "code": "PRIMEIRO999" },
  "items": [
    {
      "itemId": "item_1786746719640_j4ekjyjgd",
      "name": "Belary Premium",
      "description": null,
      "type": "RECURRING",
      "amount": 89.90,
      "quantity": 1,
      "discount": 0,
      "status": "PENDING",
      "price": {
        "priceId": "price_1786738209146_15vvse7x6",
        "amount": 89.90,
        "title": "Mensal",
        "recurrenceType": "MONTHLY",
        "trialDays": null
      },
      "product": {
        "productId": "prod_1786738209087_fhwxkyp6b",
        "name": "Belary Premium",
        "type": "RECURRING"
      }
    }
  ],
  "customer": {
    "customerId": "cus_1786746719497_9px4bs9dh",
    "name": "Lucas Ferreira Valente",
    "email": "lucas@example.com",
    "taxId": "12354358903",
    "phone": "4197424445"
  }
}
```

## Sequências típicas

**Assinatura com cartão, sem trial:**
`subscription.created` → `charge.created` → `payment.success` → `subscription.activated`

**Assinatura com trial:**
`subscription.created` → `subscription.trial` → (fim do trial) `charge.created` → `payment.success` → `subscription.activated`

**Renovação:**
`charge.created` → `payment.success` → `subscription.renewed`

**Inadimplência:**
`charge.created` → `payment.failed` (uma vez por tentativa do motor de retentativa) → `subscription.canceled`, se o dunning esgotar as tentativas

**Cancelamento no fim do ciclo:**
`subscription.cancel_scheduled` → (fim do ciclo) `subscription.canceled` com `cancelReason: "SCHEDULED_AT_PERIOD_END"`

Não existe evento de "assinatura vencida": a inadimplência é observável pelos `payment.failed` sucessivos e pelo `currentCycle.status` dos eventos seguintes.

Não dependa da ordem de chegada: ordene por `timestamp` e trate cada evento de forma idempotente.
