DEV Community

brian austin
brian austin

Posted on

How to build a Claude AI webhook handler that classifies and responds to events automatically

How to build a Claude AI webhook handler that classifies and responds to events automatically

Webhooks are everywhere. Stripe sends payment events. GitHub sends push notifications. Twilio sends SMS replies. The problem: every webhook needs custom parsing logic, and that logic gets messy fast.

What if Claude just... figured out what the webhook meant and decided what to do?

Here's how to build a universal webhook handler powered by Claude that classifies incoming events and takes action automatically.

The setup

npm install express @anthropic-ai/sdk
Enter fullscreen mode Exit fullscreen mode
// webhook-handler.js
import Anthropic from '@anthropic-ai/sdk';
import express from 'express';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const app = express();
app.use(express.json());

async function classifyAndHandle(webhookBody, source) {
  const message = await client.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 512,
    messages: [{
      role: 'user',
      content: `You are a webhook event classifier. Analyze this ${source} webhook payload and respond with JSON:
{
  "eventType": "payment_success|payment_failed|new_signup|message_received|push_event|other",
  "summary": "one sentence describing what happened",
  "urgency": "low|medium|high",
  "suggestedAction": "what the system should do next"
}

Webhook payload:
${JSON.stringify(webhookBody, null, 2)}`
    }]
  });

  try {
    return JSON.parse(message.content[0].text);
  } catch {
    return { eventType: 'other', summary: message.content[0].text, urgency: 'low', suggestedAction: 'manual review' };
  }
}
Enter fullscreen mode Exit fullscreen mode

Handling the routes

// A single endpoint handles ALL your webhooks
app.post('/webhook/:source', async (req, res) => {
  const { source } = req.params;
  const payload = req.body;

  console.log(`Received webhook from: ${source}`);

  // Acknowledge immediately (webhooks time out fast)
  res.json({ received: true });

  // Process async
  try {
    const classification = await classifyAndHandle(payload, source);

    console.log(`Event: ${classification.eventType}`);
    console.log(`Summary: ${classification.summary}`);
    console.log(`Urgency: ${classification.urgency}`);
    console.log(`Action: ${classification.suggestedAction}`);

    // Route based on classification
    await routeEvent(classification, payload, source);
  } catch (err) {
    console.error('Classification failed:', err.message);
  }
});

async function routeEvent(classification, payload, source) {
  switch (classification.eventType) {
    case 'payment_failed':
      // Send dunning email, pause account
      await handleFailedPayment(payload, classification);
      break;
    case 'new_signup':
      // Trigger onboarding sequence
      await handleNewSignup(payload, classification);
      break;
    case 'message_received':
      // Auto-reply or route to support
      await handleMessage(payload, classification);
      break;
    default:
      // Log for manual review
      console.log('Unhandled event type, logged for review');
  }
}

app.listen(3000, () => console.log('Webhook handler listening on :3000'));
Enter fullscreen mode Exit fullscreen mode

Adding Claude-powered auto-replies

For message webhooks (Twilio, Slack, email), you can generate replies too:

async function handleMessage(payload, classification) {
  // Extract message text based on source format
  const messageText = payload.Body || payload.text || payload.message || '';

  const reply = await client.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 256,
    system: 'You are a helpful support agent. Keep replies under 3 sentences. Be friendly and direct.',
    messages: [{
      role: 'user',
      content: messageText
    }]
  });

  const replyText = reply.content[0].text;
  console.log(`Auto-reply: ${replyText}`);

  // Send reply via appropriate channel
  // await twilio.messages.create({ Body: replyText, To: payload.From, From: process.env.TWILIO_NUMBER });
}
Enter fullscreen mode Exit fullscreen mode

Real-world test

Test with a simulated Stripe payment failure:

curl -X POST http://localhost:3000/webhook/stripe \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "payment_intent.payment_failed",
    "data": {
      "object": {
        "amount": 2000,
        "currency": "usd",
        "last_payment_error": {
          "message": "Your card has insufficient funds."
        }
      }
    }
  }'
Enter fullscreen mode Exit fullscreen mode

Output:

Received webhook from: stripe
Event: payment_failed
Summary: A $20 payment failed due to insufficient funds on the customer's card
Urgency: high
Action: Send dunning email immediately and retry payment in 24 hours
Enter fullscreen mode Exit fullscreen mode

Claude correctly identifies the event type without you writing a single conditional.

Why this pattern works

Traditional approach: Write a switch statement for every possible Stripe event type (there are 237 of them). Update it every time Stripe adds a new event. Repeat for every webhook source.

Claude approach: One endpoint, one prompt, handles any webhook from any source. New event type from a new vendor? Zero code changes.

The classification adds ~300ms latency — acceptable for async webhook processing where you've already acknowledged the request.

Token costs

Each classification call uses roughly 200-400 input tokens + 100 output tokens. At standard Anthropic API rates, that's about $0.001-0.003 per webhook event.

If you're processing 10,000 webhook events per month, that's $10-30 in API costs. Worth it to eliminate the maintenance burden of 237 Stripe event types.

Want to reduce costs further? You can use claude-haiku-3-5 for classification (it's excellent at structured output tasks) and reserve Sonnet/Opus for the reply generation.


If you want Claude API access at a flat rate without per-token billing anxiety, I built SimplyLouie — $2/month for unlimited Claude access. The developer API is also available if you want to build webhook handlers like this one.

What's the most annoying webhook integration you've had to build? I'm curious if Claude's classification would have made it easier.

Top comments (0)