Foto av Kirill Sh via Unsplash
Webhooks: Realtidsintegration utan polling
Bygg tillförlitliga webhook-system med garanterad leverans. Fan-out, dead letter queues, payload-validering och monitoring i produktion.
Varför webhooks slår polling
Polling är som att ringa en vän var tredje minut för att fråga om de har nyheter. Webhooks är som att din vän ringer dig när det händer något. Skillnaden i effektivitet är dramatisk.
Ett typiskt polling-scenario: din app anropar ett externt API var 30:e sekund för att kontrollera uppdateringar. Det är 2 880 requests per dag, varav kanske 50 returnerar ny data. 98% av trafiken är bortkastad.
Med webhooks får du istället exakt 50 notifikationer — en för varje faktisk händelse. Latensen minskar från uppemot 30 sekunder till millisekunder.
För grunderna i webhook-arkitektur, se även vår webhooks-guide.
Bygga en webhook-sändare
Om ditt API ska skicka webhooks behöver du mer än en enkel HTTP-POST. Asynkron leverans via en meddelandekö separerar webhook-sändningen från din huvudapplikation, så att en långsam mottagare inte blockerar din API-respons.
Implementera retry med exponentiell backoff: 5 sekunder, 30 sekunder, 5 minuter, 30 minuter, 2 timmar. Efter maximalt antal retries, flytta till en dead letter queue för manuell granskning.
Skicka alltid en minimal payload — bara event-typ och resurs-ID. Låt mottagaren hämta fullständig data via ditt API.
// Robust webhook-sändare med retry och DLQ
const RETRY_DELAYS = [5, 30, 300, 1800, 7200];
async function enqueueWebhook(
eventType: string,
resourceId: string,
subscriberUrls: string[]
) {
const payload = {
event: eventType,
resourceId,
timestamp: new Date().toISOString(),
apiUrl: "https://api.din-app.se/v2/"
+ eventType.split(".")[0]
+ "/" + resourceId,
};
const signature = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET!)
.update(JSON.stringify(payload))
.digest("hex");
for (const url of subscriberUrls) {
await queue.add("webhook-delivery", {
url, payload, signature, attempt: 0,
});
}
}
// Worker
async function deliverWebhook(job: Job) {
const { url, payload, signature, attempt } = job.data;
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Webhook-Signature": "sha256=" + signature,
"X-Webhook-ID": job.id,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(10_000),
});
if (!response.ok) {
throw new Error("HTTP " + response.status);
}
} catch (error) {
if (attempt < RETRY_DELAYS.length) {
await queue.add("webhook-delivery", {
...job.data, attempt: attempt + 1,
}, { delay: RETRY_DELAYS[attempt] * 1000 });
} else {
await dlq.add("failed-webhook", job.data);
}
}
}Webhook-mottagare i produktion
En produktionsklar webhook-mottagare måste hantera tre saker korrekt: signaturverifiering, idempotens och snabb respons.
Svara alltid med 200 OK inom 5 sekunder. Bearbeta händelsen asynkront. Avsändaren tolkar timeout som misslyckande och skickar om.
Idempotens är kritiskt eftersom du garanterat kommer att ta emot dubletter. Spara webhook-ID:t och kontrollera om händelsen redan bearbetats.
// Produktionsklar webhook-mottagare
import crypto from "crypto";
import { Queue } from "bullmq";
const processingQueue = new Queue("webhook-processing");
app.post("/webhooks/incoming",
express.raw({ type: "application/json" }),
async (req, res) => {
// 1. Verifiera signatur
const sig = req.headers["x-webhook-signature"];
const ts = req.headers["x-webhook-timestamp"];
if (!verifySignature(req.body, sig, ts)) {
return res.status(401).json({
error: "Ogiltig signatur",
});
}
// 2. Replay-skydd (max 5 min gammal)
const age = Date.now() - new Date(ts).getTime();
if (age > 5 * 60 * 1000) {
return res.status(400).json({
error: "Händelsen är för gammal",
});
}
const event = JSON.parse(req.body.toString());
const webhookId = req.headers["x-webhook-id"];
// 3. Idempotens
const exists = await db.processedWebhooks
.findUnique({ where: { webhookId } });
if (exists) {
return res.status(200).json({
status: "already_processed",
});
}
// 4. Kvittera snabbt
res.status(200).json({ status: "accepted" });
// 5. Köa för bearbetning
await processingQueue.add(event.event, {
webhookId, event,
});
}
);Monitoring och felsökning
Webhook-system som inte övervakas förvandlas till svarta hål. Implementera dashboards som visar leveransstatistik per mottagare, genomsnittlig svarstid, felfrekvens och antal retries.
Ge webhook-konsumenterna tillgång till leveransloggar. Stripe gör detta exemplariskt — du kan se varje leveransförsök, response-status och exakt payload i deras dashboard.
Sätt upp alerting för: mottagare som konsekvent returnerar fel, ovanligt hög retry-frekvens och dead letter queue-djup. En webhook-konsument som slutat svara bör automatiskt inaktiveras efter ett konfigurerat antal misslyckanden.
Erbjud en resend-funktion i ditt API. Det förenklar debugging enormt och minskar supportärenden.