Claude Skills Guide

Building Webhook Delivery Workflows with Claude Code and Upstash QStash

Webhooks are the backbone of modern event-driven architectures, but reliably delivering webhooks at scale presents significant challenges: network failures, endpoint timeouts, duplicate deliveries, and the need for sophisticated retry logic. In this guide, we’ll explore how to combine Claude Code’s AI-powered development capabilities with Upstash QStash—a serverless message queue—to build a robust webhook delivery workflow that handles failures gracefully and ensures message reliability.

Why QStash for Webhook Delivery?

Upstash QStash is a serverless message queue designed specifically for the cloud-native era. It integrates smoothly with Next.js, Cloudflare Workers, and other edge runtimes, making it an excellent choice for webhook infrastructure. QStash provides:

When you combine these capabilities with Claude Code’s ability to generate, explain, and optimize code, you get a powerful development workflow for building reliable webhook systems.

Setting Up Your Project with Claude Code

Claude Code excels at scaffolding projects and generating boilerplate code. Let’s start by creating a Next.js project with the necessary dependencies:

npx create-next-app@latest webhook-delivery --typescript
cd webhook-delivery
npm install @upstash/qstash nanoid

Claude Code can help you understand the architecture and generate the core components. Try asking Claude:

“Generate a webhook delivery system using QStash that includes a producer for sending webhooks, a consumer for processing delivery status, and retry logic with exponential backoff.”

Claude will generate the foundational code, but let’s walk through the key components to understand how they work together.

Core Webhook Delivery Architecture

The webhook delivery system consists of three main components:

1. The Webhook Producer

The producer is responsible for accepting webhook events and publishing them to QStash:

import { QStash } from '@upstash/qstash';
import { nanoid } from 'nanoid';

const qstash = new QStash({
  token: process.env.QSTASH_TOKEN!,
});

interface WebhookPayload {
  eventType: string;
  data: Record<string, unknown>;
  timestamp: number;
  retryCount: number;
}

export async function queueWebhook(
  endpoint: string,
  payload: WebhookPayload
): Promise<string> {
  const messageId = nanoid();
  
  await qstash.publish({
    url: endpoint,
    body: JSON.stringify(payload),
    headers: {
      'Content-Type': 'application/json',
      'X-Webhook-ID': messageId,
      'X-Webhook-Retry': '0',
    },
    delay: 0,
    retries: 3,
  });
  
  return messageId;
}

2. The Webhook Consumer

The consumer receives messages from QStash and attempts delivery:

import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const signature = request.headers.get('upstash-signature');
  
  // Verify the request is from QStash
  if (!signature) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  const payload = await request.json();
  const webhookId = request.headers.get('x-webhook-id');
  
  try {
    // Process the webhook
    await processWebhook(payload);
    
    return NextResponse.json({ success: true, webhookId });
  } catch (error) {
    // Return error to trigger QStash retry
    console.error('Webhook processing failed:', error);
    return NextResponse.json(
      { error: 'Processing failed' },
      { status: 500 }
    );
  }
}

async function processWebhook(payload: WebhookPayload) {
  // Your webhook processing logic here
  console.log(`Processing ${payload.eventType} webhook:`, payload.data);
}

3. The Retry Handler

QStash handles retries automatically, but you can add custom logic:

export async function retryWebhook(
  originalPayload: WebhookPayload,
  attemptNumber: number
): Promise<void> {
  const maxRetries = 3;
  
  if (attemptNumber >= maxRetries) {
    // Move to dead letter queue
    await moveToDeadLetterQueue(originalPayload);
    return;
  }
  
  // Calculate exponential backoff
  const delay = Math.pow(2, attemptNumber) * 1000;
  
  // Re-queue with delay
  await qstash.publish({
    url: process.env.WEBHOOK_ENDPOINT!,
    body: JSON.stringify({
      ...originalPayload,
      retryCount: attemptNumber + 1,
    }),
    delay: delay,
    retries: maxRetries - attemptNumber,
  });
}

Leveraging Claude Code for Optimization

Claude Code isn’t just a code generator—it can help you optimize and debug your webhook system. Here are practical ways to use Claude effectively:

1. Analyzing Failure Patterns

When your webhook delivery fails, paste the error logs to Claude and ask:

“Analyze these webhook delivery failures and suggest improvements to reduce retry attempts.”

Claude can identify patterns like:

2. Adding Monitoring and Observability

Ask Claude to enhance your system with logging:

“Add structured logging to this webhook delivery system using pino, including request/response tracing, timing metrics, and error categorization.”

3. Implementing Idempotency

Webhooks must handle duplicate deliveries gracefully. Ask Claude:

“Add idempotency checking to this webhook consumer using Upstash Redis to prevent duplicate processing.”

Claude will generate code like:

import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.REDIS_URL!,
  token: process.env.REDIS_TOKEN!,
});

export async function checkIdempotency(webhookId: string): Promise<boolean> {
  const key = `webhook:${webhookId}:processed`;
  const result = await redis.set(key, '1', { nx: true, ex: 86400 });
  return result === 'OK';
}

Best Practices for Production

When deploying your webhook system to production, keep these best practices in mind:

  1. Always verify signatures: QStash provides built-in signature verification. Never trust incoming webhooks without verification.

  2. Implement a dead letter queue: Some webhooks will fail permanently. Store these for manual review rather than retrying indefinitely.

  3. Add request timeout: Set reasonable timeouts (5-10 seconds) to prevent hanging connections.

  4. Monitor delivery metrics: Track success rates, retry counts, and latency to identify issues before users report them.

  5. Use message deduplication: QStash supports deduplication based on message IDs—essential for exactly-once semantics.

Conclusion

Building reliable webhook delivery systems requires careful attention to failure handling, retry logic, and observability. By combining Claude Code’s AI-powered development capabilities with Upstash QStash’s serverless message queue, you can rapidly build and iterate on webhook infrastructure that’s production-ready.

Claude Code helps you not just generate initial code, but also debug issues, optimize performance, and implement best practices—making the development of complex event-driven systems more accessible and maintainable.

Start building your webhook delivery workflow today, and let Claude Code guide you through the implementation details while QStash handles the reliability guarantees.

Built by theluckystrike — More at zovo.one