PIX
O PIX não tem autorização e captura separadas: ou a cobrança foi paga, ou não foi. Você cria a cobrança, exibe o QR ao pagador, e recebe um aviso quando o pagamento entra.
Criar uma cobrança
curl -X POST https://api-crpay.deskhotel.com.br/v1/pix/charges \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": 131640,
"reference": "RES-99871",
"description": "Diária 12/03 a 15/03",
"expires_in": 3600,
"payer": {
"name": "Fulano de Tal",
"document": "12345678909"
}
}'
{
"id": "pay_01KYFSX27TJEHQEF18TSH8YFQD",
"status": "pending",
"amount": 131640,
"currency": "BRL",
"payment_method": "pix",
"reference": "RES-99871",
"pix": {
"qr_code": "00020126580014BR.GOV.BCB.PIX...",
"qr_code_image": "data:image/png;base64,iVBORw0KGgo...",
"txid": "e1f2a3b4c5d6",
"end_to_end_id": null,
"expires_at": "2026-07-27T19:09:55.116Z"
},
"payer": { "name": "Fulano de Tal", "document": "12345678909" },
"paid_at": null,
"metadata": null,
"created_at": "2026-07-27T18:09:55.116Z",
"updated_at": "2026-07-27T18:09:55.116Z"
}
Escopo necessário: pix:write.
Campos
amount é inteiro em centavos. R$ 1.316,40 se envia como 131640. Decimal,
string, zero e negativo são recusados com 400 invalid_amount.
expires_in é opcional, em segundos. Sem ele, vale a expiração padrão da sua conta.
payer é opcional. document aceita CPF ou CNPJ, com ou sem pontuação.
reference é o seu identificador — número da reserva, do pedido, do que for. Ele
volta em toda consulta e no aviso de pagamento, e é por ele que você concilia.
Exibindo o QR
qr_code é o copia-e-cola (BR Code). qr_code_image já vem como data-URL, pronta
para um <img src="..."> sem requisição adicional.
Os dois representam a mesma cobrança. Ofereça ambos: quem paga no celular usa a imagem, quem paga no desktop copia o código.
Consultar uma cobrança
curl https://api-crpay.deskhotel.com.br/v1/pix/charges/pay_01KYFSX27TJEHQEF18TSH8YFQD \
-H "Authorization: Bearer $TOKEN"
Mesmo formato da criação, com status atualizado. Escopo: pix:read.
Listar cobranças
curl "https://api-crpay.deskhotel.com.br/v1/pix/charges?reference=RES-99871&status=paid&limit=20" \
-H "Authorization: Bearer $TOKEN"
{
"data": [ { "id": "pay_...", "status": "paid", "...": "..." } ],
"next_cursor": "pay_01KYFSX27TJEHQEF18TSH8YFQD"
}
Filtros: reference, status, limit, cursor. Para a próxima página, repita a
chamada passando cursor com o valor de next_cursor. Quando next_cursor vier
null, acabou.
Estados
| Estado | Significado |
|---|---|
pending |
Cobrança criada, aguardando pagamento |
paid |
Pago e confirmado |
expired |
Prazo vencido sem pagamento |
refused |
O provedor recusou a criação |
unknown |
Não foi possível confirmar; será resolvido automaticamente |
unknown é temporário. Quando a comunicação falha em um ponto em que não dá para
saber se a cobrança existe do outro lado, ela fica nesse estado e uma rotina
periódica a resolve. Não trate como falha definitiva.
Conciliação
Duas chaves acompanham cada cobrança paga:
txid— identificador da cobrança dentro do arranjo PIX, disponível desde a criaçãoend_to_end_id— identificador oficial da transação no Banco Central, preenchido quando o pagamento entra
Ambos são padrão do PIX e aparecem no extrato da sua conta bancária. É por eles que você bate o que o crpay registrou contra o que o banco liquidou.
Aviso de pagamento (webhook)
O PIX é instantâneo, e ficar consultando em laço desperdiça chamada e atrasa a confirmação para o seu cliente. Registre um endpoint e o crpay avisa.
Fale com o suporte para cadastrar a URL. Você recebe um segredo de assinatura, exibido uma única vez — guarde-o no momento do cadastro.
Eventos: pix.paid, pix.expired.
O que chega
POST /seu-endpoint HTTP/1.1
Content-Type: application/json
Crpay-Event-Type: pix.paid
Crpay-Signature: t=1753290000,v1=5d41402abc4b2a76b9719d911017c592...
{
"id": "evt_01KYFSX27TJEHQEF18TSH8YFQD",
"type": "pix.paid",
"created_at": "2026-07-27T18:14:02.441Z",
"data": {
"id": "pay_01KYFSX27TJEHQEF18TSH8YFQD",
"status": "paid",
"amount": 131640,
"reference": "RES-99871",
"paid_at": "2026-07-27T18:14:01.900Z",
"end_to_end_id": "E1234567820260727181401abcdef123"
}
}
Validando a assinatura
Valide sempre. Sem isso, qualquer um que descubra a sua URL pode enviar um
pix.paid e liberar um pedido que ninguém pagou.
O cabeçalho Crpay-Signature traz t (timestamp Unix) e v1 (HMAC-SHA256 em
hex). O que é assinado é a string {t}.{corpo} — o corpo cru, exatamente como
chegou, antes de qualquer parse.
const crypto = require('crypto');
function valido(corpoCru, cabecalho, segredo) {
const partes = Object.fromEntries(
String(cabecalho || '').split(',').map((p) => p.split('='))
);
if (!partes.t || !partes.v1) return false;
// Rejeita reenvio de uma entrega capturada tempos atrás.
if (Math.abs(Date.now() / 1000 - Number(partes.t)) > 300) return false;
const esperado = crypto
.createHmac('sha256', segredo)
.update(`${partes.t}.${corpoCru}`)
.digest('hex');
const a = Buffer.from(esperado);
const b = Buffer.from(partes.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Se você usa Express, monte express.raw({ type: 'application/json' }) nessa rota,
antes do express.json() global. Se o JSON for parseado primeiro, o corpo cru se
perde e a assinatura nunca vai bater.
Entrega e reenvio
Responda 2xx assim que receber. Qualquer outra resposta, ou um tempo acima de 15
segundos, conta como falha.
Entregas que falham são reenviadas em 1, 5, 30, 120 e 360 minutos — cerca de 8 horas de janela. Depois disso a entrega para de ser tentada, mas a cobrança segue paga e consultável pela API.
Trate o recebimento como idempotente. Um reenvio pode chegar depois de você já
ter processado a entrega original — se a sua resposta 2xx se perdeu no caminho, por
exemplo. Use o id do evento para descartar o que já foi processado.