Webhook handling (notes)

Webhook handling (notes)

Webhooks are like getting a text message. When something changes, a system sends you an update. But getting these updates right is tricky. Let's see how to handle them properly.

First, you need an endpoint to receive updates (the other system e.g. Stripe will make POST requests to this endpoint):

app.post("/webhooks/salesforce", async (req, res) => {
  // We'll add stuff here
});

Always verify it's really them sending the update. Each webhook comes with a signature:

if (!verifySignature(req.headers["x-signature"])) {
  return res.status(401).send();
}

Now the important part: respond quickly. Send a 202 Accepted right away. This tells the sender "Got it, I'll handle it". Don't make them wait:

res.status(202).send();

Process the update in the background. This prevents timeouts:

await processWebhookAsync({
  id: req.headers["webhook-id"],
  payload: req.body,
});

But what if we get the same update twice? This is where idempotency keys help. Each webhook has a unique ID. Store IDs you've processed (in your database):

app.post("/webhooks/carta", async (req, res) => {
  // Check signature first
  if (!verifySignature(req.headers["x-signature"])) {
    return res.status(401).send();
  }

  const webhookId = req.headers["webhook-id"];

  // Check if already processed
  if (await alreadyProcessed(webhookId)) {
    return res.status(200).send(); // "OK, already handled this"
  }

  // New webhook, respond with 202
  res.status(202).send(); // "Got it, will process"

  // Process in background
  await processWebhookAsync({
    id: webhookId,
    payload: req.body,
  });
});

When you're fully done processing, keep track of it. Next time this ID shows up, you'll know to skip it.

A few key things to remember:

  • Return 202 quickly, process later

  • Always check signatures

  • Use idempotency keys to prevent double processing

  • Store processed IDs (usually in a database)

  • Handle errors in your async processing

Think of it like receiving packages:

  1. Check it's from the right sender (signature)

  2. Say "got it" quickly (202)

  3. Process it later (async)

  4. Keep track of what you've processed (idempotency)