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

# Sua primeira cobrança

> Do zero ao PIX pago em sandbox, sem mover dinheiro de verdade.

Este guia vai até o fim: chave, cobrança, pagamento simulado e webhook recebido. Tudo em
sandbox, então nada aqui movimenta dinheiro.

**1. Gere uma chave de sandbox**

Entre no painel, abra [Equipe & API → Developer API](https://purin.cash/dashboard/api) e
gere uma chave de sandbox. Ela começa com `ps_test_`.

A chave inteira aparece uma única vez, na hora da criação. Se perder, revogue e gere
outra. Cada conta pode manter até 10 chaves ativas.

> [!WARNING]
>
> Chave de API é credencial de servidor. Ela nunca deve aparecer em JavaScript de
> navegador, app mobile, repositório público ou print de tela.

**2. Crie a cobrança**

O endpoint mais direto é `POST /v1/charges`: valor livre, sem precisar cadastrar
produto antes.

```bash cURL theme={null}
curl -X POST https://api.purin.cash/v1/charges \
  -H "Authorization: Bearer $PURINCASH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "valueCents": 1990,
    "description": "Plano Pro",
    "callbackUrl": "https://minhaloja.com/webhooks/purincash",
    "customer": { "name": "João Silva", "email": "joao@exemplo.com" },
    "metadata": "{\"pedido\":\"1042\"}"
  }'
```

```js Node.js theme={null}
const res = await fetch("https://api.purin.cash/v1/charges", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PURINCASH_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    valueCents: 1990,
    description: "Plano Pro",
    callbackUrl: "https://minhaloja.com/webhooks/purincash",
    customer: { name: "João Silva", email: "joao@exemplo.com" },
    metadata: JSON.stringify({ pedido: "1042" }),
  }),
});

const cobranca = await res.json();
console.log(cobranca.paymentId, cobranca.pix.brCode);
```

```python Python theme={null}
import os, json, requests

resposta = requests.post(
    "https://api.purin.cash/v1/charges",
    headers={"Authorization": f"Bearer {os.environ['PURINCASH_KEY']}"},
    json={
        "valueCents": 1990,
        "description": "Plano Pro",
        "callbackUrl": "https://minhaloja.com/webhooks/purincash",
        "customer": {"name": "João Silva", "email": "joao@exemplo.com"},
        "metadata": json.dumps({"pedido": "1042"}),
    },
    timeout=15,
)
cobranca = resposta.json()
print(cobranca["paymentId"], cobranca["pix"]["brCode"])
```

```php PHP theme={null}
$ch = curl_init("https://api.purin.cash/v1/charges");
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer " . getenv("PURINCASH_KEY"),
    "Content-Type: application/json",
  ],
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => json_encode([
    "valueCents"  => 1990,
    "description" => "Plano Pro",
    "callbackUrl" => "https://minhaloja.com/webhooks/purincash",
    "customer"    => ["name" => "João Silva", "email" => "joao@exemplo.com"],
    "metadata"    => json_encode(["pedido" => "1042"]),
  ]),
]);

$cobranca = json_decode(curl_exec($ch), true);
echo $cobranca["paymentId"], PHP_EOL, $cobranca["pix"]["brCode"];
```

A resposta vem assim:

```json Resposta 201 theme={null}
{
  "paymentId": "psc_a1b2c3d4e5f6",
  "status": "pending",
  "amountCents": 1990,
  "currency": "BRL",
  "environment": "sandbox",
  "pix": {
    "brCode": "00020126580014br.gov.bcb.pix0136...",
    "qrCodeImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
  },
  "expiresAt": "2026-03-18T12:30:00.000Z"
}
```

Guarde o `paymentId` junto do seu pedido. É por ele que você vai reconciliar tudo
depois.

> [!TIP]
>
> Quer amarrar a cobrança ao seu banco de dados sem tabela auxiliar? Use `metadata`.
> Ele volta intacto no webhook e na consulta, então dá pra guardar ali o id do pedido.

**3. Mostre o PIX pro cliente**

São dois caminhos, e você normalmente oferece os dois na mesma tela:

| Campo | O que fazer com ele |
| - | - |
| `pix.brCode` | Renderize como texto com um botão de copiar. É o copia e cola. |
| `pix.qrCodeImage` | Imagem do QR pronta pra exibir. Pode ser URL http ou data URL base64. Jogue no src de uma tag img sem inspecionar o conteudo. |

Se preferir gerar o QR você mesmo, use qualquer biblioteca de QR Code passando o
`brCode` como conteúdo. O resultado é o mesmo.

> [!WARNING]
>
> Cobrança PIX expira em 30 minutos (`expiresAt`). Depois disso o código para de
> funcionar e o status vira `expired`. Se o cliente sumiu e voltou, crie outra.

> [!NOTE]
>
> Você está em sandbox, então esse `brCode` é falso: vem como `SANDBOX_PIX_...` e
> nenhum banco reconhece. Serve pra você montar a tela; quem confirma o pagamento é o
> passo seguinte.

**4. Simule o pagamento**

Em sandbox ninguém vai pagar de verdade, então você marca como pago na mão:

```bash theme={null}
curl -X POST https://api.purin.cash/v1/sandbox/charges/psc_a1b2c3d4e5f6/simulate-paid \
  -H "Authorization: Bearer $PURINCASH_KEY"
```

Isso muda o status para `paid` e dispara o webhook na sua `callbackUrl`, igualzinho
ao que acontece em produção. Não passa por aprovação de ninguém: é essa chamada e
pronto.

> [!NOTE]
>
> Para pagamentos criados em `POST /v1/payments` o endpoint é
> `/v1/sandbox/payments/{paymentId}/simulate-paid`. A diferença é só o recurso.

**5. Receba o webhook**

A gente faz `POST` na sua URL com o corpo do evento e o header
`X-Webhook-Signature`:

```json theme={null}
{
  "event": "charge.paid",
  "paymentId": "psc_a1b2c3d4e5f6",
  "amountCents": 1990,
  "status": "paid",
  "paidAt": "2026-03-18T12:05:00.000Z",
  "customer": { "name": "João Silva", "email": "joao@exemplo.com" },
  "metadata": "{\"pedido\":\"1042\"}",
  "sandbox": true
}
```

Antes de liberar qualquer coisa pro cliente, [valide a
assinatura](https://docs.purin.cash/webhooks/assinatura.md). A sua URL é pública, então qualquer pessoa
consegue mandar um JSON dizendo `"status": "paid"`.

Sem servidor exposto ainda? Use um túnel local (`ngrok http 3000`, `cloudflared
        tunnel`) ou um coletor tipo webhook.site só pra ver o payload chegando.

**6. Confirme pela API**

Webhook é notificação, não fonte da verdade. Antes de entregar o produto, confirme:

```bash theme={null}
curl https://api.purin.cash/v1/charges/psc_a1b2c3d4e5f6 \
  -H "Authorization: Bearer $PURINCASH_KEY"
```

Se o `status` vier `paid` e o `amountCents` bater com o que você cobrou, pode
liberar.

## Passando pra produção

Troque `ps_test_` por `ps_live_`. Só isso. Nenhuma URL muda, nenhum parâmetro muda.

Antes de virar a chave, dá uma passada em [Antes de ir pra produção](https://docs.purin.cash/guias/producao.md).

> [card] [Vender produto cadastrado](https://docs.purin.cash/guias/produtos.md)
>
> Cadastre o preço uma vez e cobre por `productId`.

> [card] [Entregar automaticamente](https://docs.purin.cash/guias/entrega.md)
>
> Chave, conta ou link liberado no instante do pagamento.


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