Use case

Serverless webhook processors for APIs and providers

Stripe, GitHub, Slack, Shopify: run signature verification on the raw body, ack fast before provider timeouts, use async handoff for slow work, design idempotency for duplicate deliveries, and keep traces when retries overlap.

Last updated: 2026-06-28

Direct answer

Serverless webhook processors for APIs and providers. Dedicated functions per provider reduce accidental coupling between webhook processors.

When it fits

  • SaaS integrations
  • Payment providers
  • Git hosting events

Tradeoffs

  • Skipping signature verification “temporarily” leaves the endpoint open to forged payloads long after the shortcut is forgotten.
  • Running database writes synchronously before returning 200 OK invites provider retries—and duplicate records—whenever downstream latency spikes.

Webhook failure modes

Duplicate deliveries and slow downstream calls cause time-outs unless you design idempotency and async handoff from the start.

If you run heavy mutations before returning 2xx, providers will retry—and you will double-charge or double-write unless every path is idempotent.

One overloaded endpoint drags down unrelated user traffic.

Common shortcuts

Skipping signature verification “temporarily” leaves the endpoint open to forged payloads long after the shortcut is forgotten.

Running database writes synchronously before returning 200 OK invites provider retries—and duplicate records—whenever downstream latency spikes.

Webhook architecture: verify, ack fast, then async

Dedicated functions per provider reduce accidental coupling between webhook processors.

Pipelines continue work after a fast HTTP response so provider timeouts never block idempotent writes.

Webhook gateway tooling: auth, traces, tenant routes

Route-level auth

Enforce API keys or custom auth schemes per ingress route, before handler code runs.

Tracing

Traces show which stage failed—verify, enqueue, or apply—so you debug retries instead of guessing.

Tenant routing

Map hosts or path prefixes cleanly when every customer gets a dedicated webhook URL.

How to process webhooks on Inquir Compute

Verify signatures, acknowledge within provider timeouts, apply writes idempotently.

1

Verify raw request + dedupe event ID

Reject bad signatures and block duplicates before mutating state.

2

Return success inside timeout window

Acknowledge quickly so providers do not retry unnecessarily.

3

Enqueue heavy work + apply idempotent writes

Continue asynchronously and keep side effects replay-safe.

Provider-specific implementation patterns

Real providers have provider-specific headers and timeout constraints. Use separate functions per provider — verification logic stays small and reviewable.

webhooks/stripe.mjs
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function handler(event) {
  const rawBody = event.body ?? '';
  const sig = event.headers['stripe-signature'] ?? '';
  let evt;
  try {
    evt = stripe.webhooks.constructEvent(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    return { statusCode: 400, body: `Webhook Error: ${err.message}` };
  }
  const isNew = await db.upsertWebhookEvent(evt.id, evt.type);
  if (!isNew) return { statusCode: 200, body: 'duplicate' };
  await global.durable.startNew('process-payment', undefined, { eventId: evt.id, type: evt.type, obj: evt.data.object });
  return { statusCode: 200, body: 'ok' };
}
webhooks/shopify.mjs
import { createHmac } from 'node:crypto';

export async function handler(event) {
  const rawBody = event.body ?? '';
  const hmac = event.headers['x-shopify-hmac-sha256'];
  const digest = createHmac('sha256', process.env.SHOPIFY_WEBHOOK_SECRET).update(rawBody).digest('base64');
  if (hmac !== digest) return { statusCode: 401, body: 'invalid signature' };
  const topic = event.headers['x-shopify-topic']; // e.g. 'orders/create'
  const order = JSON.parse(rawBody);
  await global.durable.startNew('process-order', undefined, { topic, orderId: order.id });
  return { statusCode: 200, body: '' };
}

Good fit

When this works

  • SaaS integrations
  • Payment providers
  • Git hosting events

When to skip it

  • Bi-directional sockets—different transport

FAQ

Why must the raw body stay untouched for HMAC verification?

Providers sign exact bytes; JSON re-serialization or charset changes alter the payload and cause false “invalid signature” failures.

Should webhooks do heavy work before returning 200?

No—acknowledge within provider timeouts, then continue in a pipeline or background function so retries do not duplicate expensive side effects.

How do I handle duplicate webhook deliveries?

Treat provider event IDs as idempotency keys: persist “seen” before mutating data so at-least-once delivery stays safe.