Building Webhook Integrations with Claude
AI generated
Claude
>_
Claude AI · Webhooks · Backend Integration
Building Webhook Integrations with Claude
Designing signature verification, idempotency, and provider specific retry behavior correctly from the start

A webhook endpoint differs fundamentally from a classic REST API you call yourself, because you control neither the timing nor the frequency of incoming delivery and must either trust the sender initially or verify its identity cryptographically. Anyone who overlooks these quirks during design almost inevitably builds in a gap, whether that's an unchecked signature, a payment processed twice, or an endpoint that simply drops events under load. This article shows how Claude helps with a robust design of signature checking, idempotency, and provider specific retry behavior.

13 min read Webhooks HMAC Idempotency API Integration

1. Why webhooks need different reliability guarantees than normal APIs

With a self initiated API call, the calling code fully controls timing, frequency, and error handling: if a call fails, it can decide directly whether and how often to retry. With an incoming webhook, that control sits entirely with the sender, while the receiver can only react, without knowing whether a given event is arriving for the first time or is already the third delivery attempt of a previously failed one.

This inversion of control demands a fundamentally different design approach: instead of ensuring reliability through your own retry logic, a webhook endpoint must be robust against unknown, potentially repeated, and potentially malicious delivery. Claude works well for consistently enforcing exactly this mindset during design, say with the recurring question of what happens if this exact event arrives twice, out of order, or from an attacker without a valid signature.

2. Implementing HMAC signature verification correctly

Practically every serious webhook provider signs the payload it sends with a shared, previously exchanged secret via HMAC, usually based on SHA-256, and places the resulting signature in an HTTP header, say Stripe-Signature or X-Hub-Signature-256. The endpoint must independently compute the same signature from the received raw request body and compare it against the transmitted signature before the payload gets processed for content at all.

Claude can be asked deliberately to implement this verification for a specific provider API, paying particular attention to the provider's exact specification for how the string to be signed gets constructed, because some providers don't just sign the plain request body but a composed string of timestamp and body, to make replay attacks harder as well.


// Node.js: verifying the HMAC-SHA256 signature of an incoming webhook
import crypto from 'node:crypto';

function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody) // must be the UNMODIFIED raw body, not re-serialized JSON
    .digest('hex');

  const provided = Buffer.from(signatureHeader, 'hex');
  const calculated = Buffer.from(expected, 'hex');

  if (provided.length !== calculated.length) {
    return false;
  }
  return crypto.timingSafeEqual(provided, calculated);
}

3. Avoiding common mistakes in signature checking

The most common mistake in signature checking is computing the signature not against the unmodified raw request body but against JSON that the web framework has already parsed into an object and then re-serialized, which can differ minimally from the original in formatting, field order, or whitespace, causing an otherwise correct signature to appear invalid. Many web frameworks parse the body automatically by default before your own code ever gets access to the raw data, which makes this mistake common in practice.

A second, security critical mistake is comparing two signatures with an ordinary string comparison instead of a constant time comparison function like crypto.timingSafeEqual. An ordinary comparison stops at the first differing byte, making the response time minimally but measurably dependent on the number of correctly guessed leading bytes, a classic timing attack scenario that, with enough requests, can theoretically be exploited to guess a valid signature byte by byte.

4. Ensuring idempotency for duplicate event delivery

Nearly every webhook provider explicitly guarantees only at least once delivery, never exactly once, because network failures on the provider side can cause an event to get resent even though the previous delivery was actually already processed successfully, just the acknowledgment response got lost. An endpoint that unconditionally books a payment or sends an email for every incoming event will inevitably process the same action a second time on a repeat delivery.

The solution is to record every event by its provider supplied unique event id in a dedicated table before actual processing begins, and to skip processing for an already known id while still acknowledging with a successful HTTP response, so the provider stops attempting further deliveries. Claude works well for designing this pattern correctly, including the necessary database unique constraint, instead of implementing the check only at the application level without a real constraint, which carries a race condition risk under concurrent deliveries.


-- Unique event id as a hard database guarantee against duplicate processing
CREATE TABLE processed_webhook_events (
    provider        VARCHAR(50) NOT NULL,
    event_id        VARCHAR(255) NOT NULL,
    processed_at    TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (provider, event_id)
);

-- The INSERT fails in a controlled way on an already known event_id,
-- instead of booking the payment a second time.

5. Accounting for event order and out-of-order delivery

Besides duplicate deliveries, most providers also don't guarantee a strict order in which events actually arrive at the endpoint, because parallel delivery attempts and different retry timings can cause a later event to arrive before an actually earlier one. An endpoint that, say, always sets an order's status to the most recently received value can accidentally overwrite a newer state with an older one as a result.

Claude can be asked deliberately to check, for a specific event schema, whether the provider includes a timestamp or a monotonically increasing sequence number in the event, and to design logic based on that which ignores an incoming event with an older timestamp than the last one processed, instead of blindly treating every incoming event as the current state.

6. Accounting for different providers' retry behavior

Stripe, GitHub, and Shopify differ noticeably in their retry behavior on failed delivery, which directly affects how you design your own error handling. Stripe retries delivery on an error status for up to three days with exponentially growing intervals, while GitHub plans for noticeably shorter time windows and fewer retry attempts, and Shopify permanently discards events after several failed attempts within just a few hours.

These differences mean that an endpoint briefly unreachable usually still has enough time for automatic recovery through redelivery on Stripe integrations, while for Shopify integrations, active monitoring with a faster reaction time matters considerably more, because lost events there aren't necessarily redelivered automatically. Claude works well for designing a matching monitoring and alerting strategy for a specific combination of providers, tuned to these differing time windows.

7. Asynchronous processing: fast response, processing via a queue

Most providers expect an HTTP response within a few seconds and treat a timeout as failed delivery, which triggers another delivery attempt. An endpoint that handles the entire business processing, say sending several downstream notifications, synchronously within the incoming HTTP request risks a timeout, and therefore unnecessary, avoidable repeat deliveries, on every slightly slower downstream dependency.

The more robust design separates receipt from processing: the endpoint only checks the signature, records the event via the idempotency check, and immediately acknowledges delivery with a successful response, while actual business processing happens asynchronously via a queue. Claude can help design this separation cleanly and also clarify how to handle errors during the asynchronous processing itself, say via a dead letter queue for events that still couldn't be processed after several attempts.


// Express route: fast acknowledgment, processing offloaded to the queue
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['stripe-signature'];
  if (!verifyWebhookSignature(req.body, signature, process.env.STRIPE_WEBHOOK_SECRET)) {
    return res.status(400).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  const alreadyProcessed = await isEventKnown('stripe', event.id);
  if (alreadyProcessed) {
    return res.status(200).send('OK'); // already processed, acknowledge anyway
  }

  await markEventReceived('stripe', event.id);
  await webhookQueue.enqueue(event); // actual processing runs asynchronously
  res.status(200).send('OK');
});

8. Monitoring and replaying failed webhook processing

Even with clean signature checking, an idempotency guarantee, and asynchronous processing, a residual risk remains that a bug in your own processing logic or a brief outage of a downstream dependency causes an event to not get processed successfully. Without dedicated monitoring, such a failure often goes unnoticed for days, until a business department reports from outside that a certain payment or a certain order update is missing from the system.

Claude works well for designing a monitoring dashboard that shows the number of failed processing attempts per provider and event type over time, along with a replay function that deliberately resends a single event that landed in the dead letter queue through the same processing logic, instead of just logging it for manual inspection. It's important that such a replay runs through the same idempotency check as a regular delivery, so a repeated replay attempt doesn't itself cause duplicate processing.

9. Provider comparison: signature scheme and retry strategy

The following table compares signature scheme, retry strategy, and idempotency mechanism across common webhook providers.

Provider Signature scheme Retry strategy Idempotency mechanism
Stripe HMAC-SHA256 over timestamp and body Up to 3 days, exponentially growing intervals Unique event id in the payload
GitHub HMAC-SHA256 over the raw body Short time window, few retries Unique delivery id in the header
Shopify HMAC-SHA256 over the raw body Several attempts within a few hours Unique event id in the header
PayPal Certificate based signature check Several days with growing intervals Unique event id in the payload
Slack HMAC-SHA256 over timestamp and body Few, closely spaced retries Retry header for detecting repeat delivery

Mironsoft

AI-assisted development, agent workflows, and team processes

Using Claude or other AI tools on the team, but without a clear workflow?

We set up AI-assisted development workflows for teams, from CLAUDE.md conventions to subagent strategies to code review processes that combine human oversight with AI speed.

Workflow Setup

Cleanly set up CLAUDE.md, project conventions, and tool permissions for the team.

Agent Strategy

Build subagent and automation workflows for recurring development tasks.

Team Onboarding

Train developers in productive, safe use of AI coding assistants.

10. Summary

Webhook Integrations with Claude: The Essentials at a Glance

Core idea

Webhook endpoints must be designed to be robust against unknown, repeated, and potentially malicious delivery.

Key safeguard

HMAC signature checking against the unmodified raw body using a constant time comparison.

Biggest risk without protection

Duplicate processing of an event, say a payment, without a hard idempotency guarantee in the database.

Proven pattern

Fast signature and idempotency checking within the request, actual processing asynchronous via a queue.

11. FAQ: Webhook Integrations with Claude: The Essentials at a Glance

1Why do webhooks differ fundamentally from self called APIs?
Because the receiver controls neither timing nor frequency of delivery and must verify the sender's identity cryptographically.
2What must an HMAC signature be computed against?
Against the unmodified raw request body, not against an already parsed and re-serialized JSON object.
3Why is an ordinary string comparison risky for signatures?
Because it stops at the first differing byte, which is theoretically exploitable for timing attacks.
4What delivery guarantee do most webhook providers offer?
Only at least once, never exactly once, so duplicate deliveries are possible at any time.
5How can duplicate processing be prevented reliably?
Via a unique event id with a hard database unique constraint, checked before actual processing begins.
6Why isn't event order guaranteed with webhooks?
Because parallel delivery attempts and different retry timings can let a later event arrive before an earlier one.
7How long does Stripe retry a failed event delivery?
Up to three days with exponentially growing intervals.
8Why should business processing run asynchronously via a queue?
So slow downstream dependencies don't cause timeouts and unnecessary repeat deliveries.
9What happens when an endpoint responds successfully to an already known event?
The provider treats delivery as successful and makes no further delivery attempts.
10What is a dead letter queue used for in webhook processing?
It collects events that still couldn't be processed after several attempts, for later manual review.