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

# card_payment.paid

> Mesma ideia do PIX, dois campos com nome diferente. É onde o handler genérico quebra.

Quando um pagamento no cartão é confirmado, a gente faz `POST` na `callbackUrl` daquela
cobrança.

```json theme={null}
POST https://minhaloja.com/webhooks/purincash
Content-Type: application/json
X-Webhook-Signature: a3f5b8c2e1d4f7a9b6c8d2e5f1a4b7c9d2e5f8a1b4c7d0e3f6a9b2c5d8e1f4a7
X-Webhook-Id: card_payment.paid:JUE7Y9MPSX

{
  "event": "card_payment.paid",
  "orderCode": "JUE7Y9MPSX",
  "amount": 49.90,
  "status": "paid",
  "paidAt": "2026-03-18T12:05:00.000Z",
  "customer": { "name": "João Silva", "email": "joao@exemplo.com" },
  "metadata": "{\"pedido\":\"1042\"}"
}
```

## As duas diferenças que importam

<Warning>
  O identificador é **`orderCode`**, não `paymentId`.

  O valor vem em **`amount`** (reais, decimal), não em `amountCents`.
</Warning>

Se o seu handler é genérico, normalize na entrada em vez de espalhar `if` pelo código:

```js theme={null}
function normalizar(evento) {
  if (evento.event === "card_payment.paid") {
    return {
      id: evento.orderCode,
      valorCents: Math.round(evento.amount * 100),
      pagoEm: evento.paidAt,
      customer: evento.customer,
      metadata: evento.metadata,
    };
  }

  // payment.paid e charge.paid
  return {
    id: evento.paymentId,
    valorCents: evento.amountCents,
    pagoEm: evento.paidAt,
    customer: evento.customer,
    metadata: evento.metadata,
  };
}
```

<Tip>
  `Math.round(amount * 100)` e não `amount * 100`. Ponto flutuante transforma `49.90 * 100`
  em `4989.999999999999`, e o `!==` contra o valor esperado passa a falhar de forma
  aleatória.
</Tip>

## Campos

<ResponseField name="event" type="string" required>
  Sempre `card_payment.paid`.
</ResponseField>

<ResponseField name="orderCode" type="string" required>
  Código do pedido no cartão. É o mesmo usado em `GET /v1/card-payments/{orderCode}`.
</ResponseField>

<ResponseField name="amount" type="number" required>
  Valor em reais, decimal.
</ResponseField>

<ResponseField name="status" type="string" required>
  Sempre `paid` neste evento.
</ResponseField>

<ResponseField name="paidAt" type="string" required>
  Data e hora da confirmação, em ISO 8601.
</ResponseField>

<ResponseField name="customer" type="object">
  `name` e `email`, quando informados na criação.
</ResponseField>

<ResponseField name="metadata" type="string">
  A string JSON enviada na criação. Vem `null` quando não houve.
</ResponseField>

## Garantias

As mesmas do PIX: assinatura HMAC em `X-Webhook-Signature`, idempotência em
`X-Webhook-Id`, timeout de 5 segundos e até 7 tentativas com backoff. Detalhes em
[Visão geral](/webhooks/visao-geral).

<Warning>
  Cartão não tem webhook em sandbox, porque cartão não roda em sandbox. Para testar o
  handler, faça uma cobrança real de valor baixo em produção.
</Warning>

## Lembrete sobre a successUrl

O cliente cair na sua `successUrl` não é confirmação de pagamento. É só o navegador
voltando, e a URL pode ser aberta na mão por qualquer um. Libere o produto por este
webhook ou por `GET /v1/card-payments/{orderCode}`.
