</>
APIGuiden.se
Webhooks9 min2026-03-03

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.

WebhooksEventsIntegrationReal-time

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.json
json
// 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.

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

webhooks/processor.ts
typescript
// 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);
  }
}

Letar du efter pålitlig hosting för dina API-projekt?

Kolla in mehosting.com