Serverless webhook processing on your domain
Verify webhook signatures, acknowledge within provider timeouts, and continue heavy work in serverless functions, jobs, or pipelines on your own domain. Stable URLs, raw bodies for signatures, then async handoff.
Last updated: 2026-04-20
Answer first
Direct answer
Serverless webhook processing on your domain. One function per provider (or per event type) keeps verification logic small and reviewable. You are not mixing cookie-based user auth with machine credentials in the same serverless handler.
When it fits
- You need stable HTTPS ingress for providers and a serverless webhook processor on a custom domain (gateway + DNS in your control).
- You want isolation between webhook traffic and customer-facing APIs.
Tradeoffs
- A single monolithic app endpoint often mixes auth modes for users and machines, complicating observability.
- Running handlers inline with your main API couples scaling dimensions you wanted to keep separate.
Workload and what breaks
Problems with webhook handlers
SaaS vendors retry liberally. If your handler does thirty seconds of database work before it responds, you invite timeouts, duplicate deliveries, and angry on-call threads.
Skipping HMAC verification “just for staging” or forgetting idempotency keys feels fine until two identical events create two charges.
Trade-offs
Why inline webhook processing breaks
A single monolithic app endpoint often mixes auth modes for users and machines, complicating observability.
Running handlers inline with your main API couples scaling dimensions you wanted to keep separate.
How Inquir helps
Serverless webhook processing in Inquir
One function per provider (or per event type) keeps verification logic small and reviewable. You are not mixing cookie-based user auth with machine credentials in the same serverless handler.
Because routes are first-class configuration, security reviewers can read which URLs exist, which keys they expect, and how they differ from customer-facing APIs.
What you get
Webhook processing features
Isolated webhook routes
Map provider-specific paths to small handlers with narrow permissions.
Execution traces
Inspect bodies (redacted) and timings when a provider pauses delivery.
Async handoff to pipelines
Ack fast, continue processing in a pipeline or job when work is heavier than a timeout window.
What to do next
How to process webhooks on your domain
Verify signatures in serverless handlers, acknowledge within provider timeouts, and apply writes idempotently.
Verify
Confirm signatures and parse events defensively.
Ack or defer
Return quickly; enqueue durable work if needed.
Apply
Update datastore idempotently using provider event IDs.
Code example
Provider-specific webhook patterns
Gateway passes API Gateway-style events: body is a string (raw bytes for signing), headers as received. Keep one function per provider so signature verification stays small and reviewable.
export async function handler(event) { const rawBody = event.body ?? ''; // Stripe signs raw bytes — never parse before verifying if (!stripe.webhooks.verifySignature(rawBody, event.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET)) { return { statusCode: 400, body: 'invalid signature' }; } const evt = JSON.parse(rawBody); // Store evt.id before side effects to guard against duplicate delivery await db.upsertWebhookEvent(evt.id, evt.type); await enqueueFulfillment(evt.id, evt.type, evt.data.object); return { statusCode: 200, body: 'ok' }; }
import { createHmac, timingSafeEqual } from 'node:crypto'; export async function handler(event) { const body = event.body ?? ''; const sigHeader = (event.headers['x-hub-signature-256'] ?? '').replace('sha256=', ''); const expected = createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET).update(body).digest('hex'); if (!timingSafeEqual(Buffer.from(sigHeader, 'hex'), Buffer.from(expected, 'hex'))) { return { statusCode: 401, body: 'invalid signature' }; } const eventType = event.headers['x-github-event']; const payload = JSON.parse(body); if (eventType === 'push') await enqueueIndexing(payload.repository.full_name, payload.after); else if (eventType === 'pull_request') await enqueuePrWork(payload); return { statusCode: 200, body: 'accepted' }; }
export async function handler(event) { const params = new URLSearchParams(event.body ?? ''); // Slack slash commands must respond within 3 seconds — // kick off the slow work in a pipeline job, return immediately await enqueueSlackWork({ command: params.get('command'), userId: params.get('user_id'), channelId: params.get('channel_id'), text: params.get('text'), responseUrl: params.get('response_url'), }); return { statusCode: 200, body: '' }; }
When it fits
Good fit
When this works
- You need stable HTTPS ingress for providers and a serverless webhook processor on a custom domain (gateway + DNS in your control).
- You want isolation between webhook traffic and customer-facing APIs.
When to skip it
- You only consume webhooks into a SaaS iPaaS and never touch the runtime.
FAQ
FAQ
How do I handle slow downstream work?
Acknowledge the webhook quickly, then continue in a serverless async pipeline or job so provider timeouts do not stall critical side effects.
Can I pin routes per tenant?
Multi-tenant routing patterns let you segment paths or hosts; see the dedicated feature page for details.
What about replay attacks?
Combine signature verification, timestamps where supported, and idempotent writes keyed by provider identifiers.