Y Yuupe Pay API v1Painel
Depois da venda

Webhooks

A Yuupe avisa o seu servidor a cada mudança de status, com assinatura HMAC e reenvio por 24 horas.

Cadastro

Eventos

EventoQuando
pedido.aprovadoPix pago ou cartão aprovado
pedido.recusadoCartão ou Pix recusado, ou confirmação do banco não concluída
pedido.expiradoPix não pago no prazo, ou 3DS não concluído em 5 minutos
pedido.estornadoValor devolvido por completo
pedido.estorno_parcialParte do valor devolvida
pedido.contestadoChargeback aberto pelo comprador

Formato da entrega

HTTP
POST https://loja.exemplo.com/webhooks/yuupe-pay
Content-Type: application/json
User-Agent: YuupePay-Webhook/1.0
X-Yuupe-Signature: t=1792170000,v1=5f2b…e9
X-Yuupe-Evento: pedido.aprovado
X-Yuupe-Entrega: 8c1d…

{
  "id": "0b6f6d1e-6a51-4a5e-9c43-1f1d2a8b7c11",
  "tipo": "pedido.aprovado",
  "criado_em": "2026-10-16T16:35:04Z",
  "ambiente": "producao",
  "pedido": {
    "id": "3f2a9c1e-5b7d-4e8a-9c21-7d4e5f6a8b90",
    "numero_pedido": "NIC-2026-000123",
    "status": "aprovado",
    "metodo": "pix",
    "valor_centavos": 15980,
    "pago_em": "2026-10-16T16:35:03Z",
    "…": "o mesmo formato do GET /pedidos/{id}"
  }
}

pedido tem o mesmo formato do objeto Pedido, inclusive motivo nos recusados.

Verificar a assinatura

v1 é o HMAC-SHA256, com o segredo daquele endereço, de `${t}.${corpo}`, onde corpo é o corpo cru da requisição. Recuse assinaturas com mais de 5 minutos.

import { createHmac, timingSafeEqual } from 'node:crypto'

// Use o corpo CRU (string/Buffer), antes de qualquer JSON.parse.
export function assinaturaValida(cabecalho, corpoCru, segredo) {
  const partes = Object.fromEntries(String(cabecalho).split(',').map((p) => p.split('=')))
  const t = Number(partes.t)
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false   // mais de 5 min: recusa
  const esperado = createHmac('sha256', segredo).update(`${t}.${corpoCru}`).digest('hex')
  const a = Buffer.from(esperado), b = Buffer.from(partes.v1 ?? '')
  return a.length === b.length && timingSafeEqual(a, b)
}

// Express:
app.post('/webhooks/yuupe-pay', express.raw({ type: 'application/json' }), (req, res) => {
  const cru = req.body.toString('utf8')
  if (!assinaturaValida(req.get('X-Yuupe-Signature'), cru, process.env.YUUPE_PAY_WEBHOOK_SEGREDO)) {
    return res.status(401).end()
  }
  const evento = JSON.parse(cru)
  // descarte repetidos pelo evento.id; depois trate evento.tipo e evento.pedido
  res.status(200).end()
})
Corpo cru
Calcule a assinatura sobre o corpo exatamente como chegou. Se o framework já converteu para JSON, a assinatura não bate (espaços e ordem mudam).

Reenvios e repetidos