Pular para o conteúdo

Webhooks

Webhooks

Com os webhooks, você recebe notificações em tempo real quando ocorrem eventos no seu workspace — por exemplo, quando um código QR é escaneado.

Criar um Webhook

Terminal window
POST /v1/webhooks
{
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"secret": "my-secret-key-min-16-chars"
}

Resposta

{
"id": "wh_abc123",
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"is_active": true,
"secret": "my-secret-key-min-16-chars",
"secret_hint": "my-s…",
"created_at": "2026-03-15T10:00:00.000Z"
}

Tipos de Eventos

EventoDescrição
*Todos os eventos
qr.createdCódigo QR criado
qr.updatedCódigo QR atualizado (URL, status, tags)
qr.deletedCódigo QR excluído
qr.scannedCódigo QR escaneado
qr.flaggedCódigo QR marcado como inseguro
webhook.pingPing de teste (via endpoint /ping)

Formato do Payload

Todos os payloads de webhook têm este formato:

{
"id": "evt_4f0c2a91d7e84b3c9a1f",
"type": "qr.scanned",
"created": "2026-03-15T10:00:00.000Z",
"data": {
"code_id": "qr_abc123",
"short_code": "r7f3Kx",
"scanned_at": "2026-03-15T10:00:00.000Z",
"country": "AT",
"device_type": "mobile",
"os": "iOS",
"browser": "Safari",
"language": "de",
"ab_variant": null,
"redirected_to": "https://example.com/landing"
}
}

Eventos de código: qr.created e qr.updated

Nestes dois eventos, data é o código exatamente como GET /v1/codes/:id o fornece. Isso inclui appearance, as cores salvas no código (null representa o preto ou branco padrão):

{
"id": "evt_9b2e41c07a5d4f18b3c6",
"type": "qr.updated",
"created": "2026-09-28T10:00:00.000Z",
"data": {
"id": "qr_abc123",
"short_code": "r7f3Kx",
"status": "live",
"appearance": { "foreground_color": "#1F4E79", "background_color": null },
"updated_at": "2026-09-28T10:00:00.000Z"
}
}

O exemplo está abreviado. Todos os campos estão disponíveis na API de Códigos.

Verificação de Assinatura

O qr3.app assina cada requisição de webhook com HMAC-SHA256:

X-QR3-Signature: sha256=a1b2c3d4e5f6...
X-QR3-Event: qr.scanned
X-QR3-Delivery: wdl_abc123
X-QR3-Attempt: 1
User-Agent: qr3-webhooks/1.0

Implementar a Verificação

import crypto from "crypto";
function verifySignature(
payload: string,
signature: string | undefined,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
const expectedBytes = Buffer.from(`sha256=${expected}`);
const receivedBytes = Buffer.from(signature ?? "");
return (
expectedBytes.length === receivedBytes.length &&
crypto.timingSafeEqual(expectedBytes, receivedBytes)
);
}
// In Express: verify the raw body — re-serialised JSON breaks the signature.
app.post(
"/webhooks/qr3",
express.raw({ type: "application/json" }),
(req, res) => {
const raw = req.body.toString("utf8");
const signature = req.headers["x-qr3-signature"] as string | undefined;
if (!verifySignature(raw, signature, process.env.QR3_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(raw);
console.log(event.type, event.data);
res.json({ received: true });
},
);

Lógica de Tentativas

Em caso de erros (HTTP >= 300 ou timeout), o qr3.app tenta reenviar a entrega:

TentativaAtraso
1Imediatamente
21 minuto
35 minutos

Após 10 erros consecutivos, o webhook é desativado automaticamente.

Garantia de Entrega

O qr3.app entrega pelo menos uma vez, não exatamente uma. Por isso, o mesmo evento pode chegar mais de uma vez ao seu endpoint: como nova tentativa após um erro ou como repetição de uma tentativa que já foi enviada.

  • Evite duplicidades usando o id do payload (evt_…). Ele pertence ao evento e permanece igual em cada tentativa e em cada repetição. X-QR3-Delivery identifica a tentativa individual: uma repetição dessa tentativa traz o mesmo valor, uma nova tentativa traz um valor novo. Confirme com 2xx um id que você já conhece, não com 4xx: um 4xx conta como tentativa com falha e, após 10 erros consecutivos, o webhook é desativado automaticamente.
  • Uma tentativa que ainda está pending uma hora após ser criada (por exemplo, porque sua mensagem na fila se perdeu) é definida como failed por uma limpeza diária; status_code e response_time_ms permanecem null. Isso não é uma falha do seu endpoint e não conta para os 10 erros consecutivos. O log armazena para isso o texto Delivery was not completed in time — closed by cleanup; o endpoint dos logs de envio não o retorna. O fechamento não é definitivo: se a mensagem ainda chegar, ela é entregue e seu resultado real substitui esse texto.

Logs de Envio

Terminal window
GET /v1/webhooks/:id/deliveries
{
"data": [
{
"id": "wdl_abc123",
"event_type": "qr.scanned",
"status": "success",
"status_code": 200,
"response_time_ms": 145,
"attempt": 1,
"created_at": "2026-03-15T10:00:00.000Z"
}
]
}

As tentativas de entrega são mantidas por 90 dias (o dia UTC atual e os 89 anteriores) e depois eliminadas automaticamente. Além dos campos mostrados, o log armazena o payload do evento e o primeiro quilobyte (1 KB) da resposta do endpoint; este endpoint não retorna nenhum dos dois.

Ping de Teste

Teste um webhook imediatamente:

Terminal window
POST /v1/webhooks/:id/ping

Isso envia um evento webhook.ping para a URL do seu endpoint.