Webhooks (respostas)
Em vez de deixar um processo esperando no ask(), você pode criar o grupo de perguntas com um callback_url. Quando houver resposta (ou o prazo acabar), o notific.me envia a resposta assinada para essa URL. Essas chamadas de saída são os webhooks. (As chamadas que outros sistemas enviam para o notific.me são os hooks.)
const handle = await notific.createAsk({ title: "Approve refund #881?", questions: [{ id: "ok", type: "yesno", text: "Approve?" }], callbackUrl: "https://crm.example.com/hooks/notificme",});// handle.id, handle.status === "open"O que chega
Seção intitulada “O que chega”Um POST no seu callback_url com a resposta no corpo JSON e estes cabeçalhos:
Notificme-Signature: t=<unix>,kid=<key id>,sig=<base64url(Ed25519(t + "." + body))>Notificme-Webhook-Id: <id> (o mesmo em toda nova tentativa: use para deduplicar)Notificme-Topic: replyNotificme-Attempt: 1Entrega e novas tentativas
Seção intitulada “Entrega e novas tentativas”- A resposta e o webhook são gravados na mesma transação (outbox transacional), então a decisão nunca se perde.
- Qualquer
2xxconta como entregue. Erros e timeouts (15 s) são repetidos com backoff exponencial por até 24 horas. - Webhooks no app.notific.me lista cada entrega com status, tentativas e último erro, e permite reenviar.
Verificando a assinatura
Seção intitulada “Verificando a assinatura”O servidor assina com Ed25519. As chaves públicas ficam em <api>/.well-known/notificme-keys.json (JWKS). Assinaturas com mais de 5 minutos de diferença do seu relógio são rejeitadas.
import express from "express";import { NotificMe, expressWebhook, expressWebhookOf } from "@notificme/sdk";
const notific = new NotificMe();const app = express();
// Put the middleware before any JSON parser (it reads the raw body).app.post("/hooks/notificme", expressWebhook({ key: notific.keysUrl }), (req, res) => { const webhook = expressWebhookOf(req)!; // verified console.log(webhook.reply?.status, webhook.reply?.asText); res.sendStatus(204);});import Fastify from "fastify";import { NotificMe, fastifyWebhookPlugin } from "@notificme/sdk";
const notific = new NotificMe();const app = Fastify();
await app.register(fastifyWebhookPlugin, { prefix: "/hooks/notificme", key: notific.keysUrl, handler: async (webhook) => { console.log(webhook.reply?.asText); }, // answers 204 when the handler returns nothing});import { Hono } from "hono";import { NotificMe, honoWebhook, type NotificMeWebhook } from "@notificme/sdk";
const notific = new NotificMe();const app = new Hono<{ Variables: { notificme: NotificMeWebhook } }>();
app.post("/hooks/notificme", honoWebhook({ key: notific.keysUrl }), (c) => { const webhook = c.get("notificme"); console.log(webhook.reply?.asText); return c.body(null, 204);});import { receiveWebhookRequest } from "@notificme/sdk";
const keys = "https://api.notific.me/.well-known/notificme-keys.json";
export async function POST(request: Request) { try { const webhook = await receiveWebhookRequest(request, keys); console.log(webhook.reply?.asText); return new Response(null, { status: 204 }); } catch { return new Response(null, { status: 401 }); }}Todo helper responde 401 {"error":{"code":"invalid_signature"}} quando a assinatura é inválida, e o seu handler nunca vê essa requisição.
Baixo nível
Seção intitulada “Baixo nível”import { receiveWebhook, verifyWebhook } from "@notificme/sdk";
// Throws WebhookVerificationError (error.reason says why).await verifyWebhook(rawBody, headers["notificme-signature"], keys);
// Verify and parse: headers, JSON payload and, for the "reply" topic, a typed result.const webhook = await receiveWebhook(rawBody, headers, keys, { questions });webhook.reply?.answers.ok;A chave pode ser a URL das chaves (baixada e guardada em cache), uma string base64url, os bytes, um JWK ou um JWKS. WebhookVerificationError.reason é missing_header, malformed_header, timestamp_out_of_tolerance, unknown_key, invalid_key ou invalid_signature.