</>
APIGuiden.se
Gron nätverksenhet med fiberoptiska anslutningar

Foto av Kirill Sh via Unsplash

Webhooks12 min2026-03-12

Webhooks: Realtidsintegration utan polling

Bygg tillförlitliga webhook-system med garanterad leverans. Fan-out, dead letter queues, payload-validering och monitoring i produktion.

WebhooksEventsKöerIntegration

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.

webhooks/sender.ts
typescript
// 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.

webhooks/receiver.ts
typescript
// 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.