Webhooks: Realtidsintegrationer med händelsedriven arkitektur
Implementera pålitliga webhooks med korrekt signaturverifiering, retry-logik och felhantering. En komplett guide från setup till produktion.
Webhooks vs polling
Webhooks vänder på kommunikationsmodellen. Istället för att din applikation upprepat frågar en extern tjänst om det finns ny data (polling), skickar tjänsten automatiskt en HTTP-request till din endpoint när en händelse inträffar. Detta ger realtidsuppdateringar utan onödig nätverkstrafik.
Typiska användningsfall inkluderar betalningsnotifikationer (Stripe), commit-hooks (GitHub), meddelandehantering (Slack) och orderuppdateringar (e-handel). Webhooks är fundamentala i modern eventdriven arkitektur.
// Webhook payload-exempel (Stripe betalning)
{
"id": "evt_1ABC234",
"type": "payment_intent.succeeded",
"created": 1709654321,
"data": {
"object": {
"id": "pi_1XYZ789",
"amount": 29900,
"currency": "sek",
"status": "succeeded",
"customer": "cus_ABC123",
"metadata": {
"order_id": "order-456"
}
}
}
}Signaturverifiering
Säkerhet är kritiskt för webhooks. Vem som helst kan skicka en HTTP-request till din endpoint om URL:en läcker. Signaturverifiering säkerställer att payloaden verkligen kommer från den förväntade avsändaren och inte har manipulerats.
De flesta tjänster använder HMAC-SHA256: de signerar payloaden med en delad hemlighet och skickar signaturen i en header. Din server beräknar samma signatur och jämför. Om de matchar är payloaden autentisk.
import crypto from "crypto";
import express from "express";
const router = express.Router();
// VIKTIGT: Använd raw body för signaturverifiering
router.post(
"/webhooks/stripe",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["stripe-signature"];
const secret = process.env.WEBHOOK_SECRET;
// Verifiera signaturen
const expectedSig = crypto
.createHmac("sha256", secret)
.update(req.body)
.digest("hex");
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
)) {
console.error("Webhook signatur ogiltig");
return res.status(401).send("Ogiltig signatur");
}
// Signatur verifierad - bearbeta händelsen
const event = JSON.parse(req.body);
console.log("Verifierad webhook:", event.type);
// Svara snabbt med 200 (bearbeta async)
res.status(200).send("OK");
// Bearbeta händelsen i bakgrunden
processWebhookEvent(event).catch(console.error);
}
);Retry-logik och idempotens
Webhooks kan misslyckas av olika skäl: nätverksproblem, serverfel eller timeouts. Avsändaren skickar därför ofta samma webhook flera gånger (retry). Din endpoint måste vara idempotent: att bearbeta samma händelse två gånger ska ge samma resultat som en gång.
Lösningen är att spara event-ID:t och kontrollera om händelsen redan har bearbetats. Använd exponentiell backoff om du själv skickar webhooks, och svara alltid snabbt med en 200-statuskod innan du påbörjar tung bearbetning.
// Idempotent webhook-hantering med databas
async function processWebhookEvent(event) {
// Kontrollera om vi redan bearbetat denna händelse
const existing = await db.webhookEvents.findUnique({
where: { eventId: event.id },
});
if (existing) {
console.log("Händelse redan bearbetad:", event.id);
return;
}
// Spara händelsen som bearbetad (atomärt)
await db.webhookEvents.create({
data: {
eventId: event.id,
type: event.type,
processedAt: new Date(),
},
});
// Hantera olika händelsetyper
switch (event.type) {
case "payment_intent.succeeded":
await handlePaymentSuccess(event.data.object);
break;
case "customer.subscription.deleted":
await handleSubscriptionCanceled(event.data.object);
break;
default:
console.log("Ohanterad händelsetyp:", event.type);
}
}
// Exponentiell backoff för retry vid sändning
async function sendWebhook(url, payload, attempt = 1) {
const MAX_RETRIES = 5;
try {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) throw new Error("HTTP " + response.status);
} catch (error) {
if (attempt >= MAX_RETRIES) throw error;
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s...
await new Promise((r) => setTimeout(r, delay));
return sendWebhook(url, payload, attempt + 1);
}
}Fler guider
Letar du efter pålitlig hosting för dina API-projekt?
Kolla in mehosting.com