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:

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.