Documentação da API

Eventos, assinatura HMAC e reenvio

Enviamos um POST JSON para a URL cadastrada em Webhooks no painel (uma de produção e, opcionalmente, outra de sandbox). Sem URL de sandbox, os eventos de teste vão para a de produção com "livemode": false.

Formato

{
  "id": "evt_3f0c9a...",
  "object": "event",
  "type": "payment.paid",
  "api_version": "2026-09-28",
  "created": 1759413600,
  "livemode": true,
  "data": {
    "object": {
      "object": "payment",
      "id": 1842,
      "order_id": "pedido-2001",
      "status": "paid",
      "method": "europix",
      "amount": "49.90",
      "fee_amount": "1.50",
      "net_amount": "48.40",
      "currency": "EUR",
      "description": "Pedido 2001",
      "customer": {
        "name": "Ana Souza",
        "email": "ana@exemplo.com"
      },
      "refunded": false,
      "refund_amount": null,
      "paid_at": "2026-10-02T14:03:11+00:00",
      "expired_at": null,
      "refunded_at": null,
      "created_at": "2026-10-02T14:01:52+00:00",
      "metadata": {},
      "livemode": false,
      "public_token": "pt_8bZ2mQ..."
    }
  }
}
HeaderConteúdo
X-Uneway-EventTipo do evento
X-Uneway-Event-IdId do evento (use para idempotência)
X-Uneway-Delivery-IdId da entrega (cada tentativa/reenvio)
X-Uneway-TimestampUnix timestamp da assinatura
X-Uneway-Signaturet=<unix>,v1=<hex>
X-Uneway-Livemodetrue ou false

Os mesmos headers também são enviados com o prefixo X-Uniway-* por compatibilidade.

Verificar a assinatura (HMAC-SHA256)

A assinatura é HMAC-SHA256(segredo, "<t>.<corpo cru>") em hexadecimal. O segredo (whsec_...) fica em Webhooks no painel. Recuse timestamps com mais de 5 minutos de diferença.

Node
import crypto from 'node:crypto';

// Use o corpo CRU (Buffer/string), antes de qualquer JSON.parse
export function verifyUneway(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const got = Buffer.from(parts.v1 || '', 'hex');
  const exp = Buffer.from(expected, 'hex');
  return got.length === exp.length && crypto.timingSafeEqual(got, exp);
}

// Express
app.post('/webhooks/uneway', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifyUneway(req.body.toString('utf8'), req.get('X-Uneway-Signature'), process.env.UNEWAY_WEBHOOK_SECRET)) {
    return res.status(400).send('assinatura inválida');
  }
  const event = JSON.parse(req.body);
  // trate event.type de forma idempotente (event.id)
  res.sendStatus(200);
});
PHP
<?php
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_UNEWAY_SIGNATURE'] ?? '';
$secret = getenv('UNEWAY_WEBHOOK_SECRET');

$parts = [];
foreach (explode(',', $header) as $piece) {
    [$k, $v] = array_pad(explode('=', trim($piece), 2), 2, '');
    $parts[$k] = $v;
}
$t = (int) ($parts['t'] ?? 0);
$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);

if (!$t || abs(time() - $t) > 300 || !hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(400);
    exit('assinatura inválida');
}

$event = json_decode($raw, true);
// trate $event['type'] de forma idempotente ($event['id'])
http_response_code(200);
curl / openssl
# Recalcule a assinatura de um corpo salvo (body.json) para depurar
T=1759413600
SECRET=whsec_...
printf '%s.%s' "$T" "$(cat body.json)" | openssl dgst -sha256 -hmac "$SECRET"
# compare com o v1= do header X-Uneway-Signature: t=$T,v1=<hex>

Entrega, retentativas e reenvio

  • Responda 2xx em até 10 segundos. Processe em segundo plano se precisar.
  • Falhas são reenviadas após 1 min, 5 min, 30 min, 2 h e 12 h (até 6 tentativas).
  • O mesmo evento pode chegar mais de uma vez: trate pelo id do evento.
  • No painel você vê cada entrega (status HTTP, tempo, corpo da resposta) e pode reenviar qualquer uma.
  • Enviar evento de teste manda um webhook.test assinado para conferir a URL e a verificação.

Catálogo de eventos

EventoQuando
payment.createdCobrança criada (aguardando pagamento).
payment.paidPagamento confirmado pelo provedor. Libere o pedido aqui.
payment.failedPagamento recusado ou com erro.
payment.expiredPrazo de pagamento esgotado.
payment.cancelledCobrança cancelada.
payment.refundedPagamento estornado (total ou o último estorno parcial).
refund.createdUm estorno (total ou parcial) foi registrado; um evento por estorno.
withdrawal.createdRepasse solicitado.
withdrawal.approvedRepasse aprovado.
withdrawal.processingRepasse enviado ao banco.
withdrawal.cancelledRepasse cancelado.
withdrawal.completedRepasse concluído (dinheiro na conta).
withdrawal.failedRepasse falhou; o valor volta ao saldo.
withdrawal.rejectedRepasse recusado na análise.
dispute.createdDisputa aberta (chargeback, MED do Pix...). O valor fica retido.
dispute.updatedDisputa atualizada (prazo, evidências, status).
dispute.closedDisputa encerrada (ganha ou perdida).
subscription.createdAssinatura criada.
subscription.renewedCiclo renovado: a cobrança do novo período foi paga.
subscription.past_dueCobrança do ciclo não foi paga no prazo (inadimplente).
subscription.canceledAssinatura cancelada.
subscription.pausedAssinatura pausada.
subscription.resumedAssinatura retomada.
webhook.testEvento de teste enviado pelo painel (Webhooks → Enviar evento de teste).

GET /developer/events

Catálogo de eventos

Lista os tipos de evento que podem chegar no seu webhook.

curl -X GET https://api.uneway.pro/api/v1/developer/events \
  -H "Authorization: Bearer uw_test_SUA_CHAVE"

Resposta (exemplo)

{
  "success": true,
  "data": [
    {
      "type": "payment.paid"
    },
    {
      "type": "payment.refunded"
    }
  ]
}