How to Set Up Webhooks: A Step-by-Step Guide

Follow a practical, end-to-end guide to setting up secure webhooks for the Pet Store API, from choosing a public HTTPS endpoint and verifying HMAC signatures to registering events, testing idempotency, handling retries with queues, monitoring health, and safely deploying to production.

TRY NANO BANANA FOR FREE

How to Set Up Webhooks: A Step-by-Step Guide

TRY NANO BANANA FOR FREE
Contents

Webhooks connect systems without constant polling. This guide walks through setting up webhooks from registration to production deployment.

Step 1: Choose Your Endpoint URL

Decide where webhooks will be delivered. This is your public-facing endpoint that receives POST requests.

Requirements:

  • Must be publicly accessible via HTTPS
  • Should return status 200 quickly
  • Needs to handle the webhook payload

For development, use ngrok to expose local servers:

ngrok http 3000

This creates a public URL like https://abc123.ngrok.io. Use this as your webhook URL during development.

For production, deploy a dedicated endpoint:

// Express.js endpoint
app.post('/api/webhooks/petstore', express.json(), (req, res) => {
  // Process webhook
  res.status(200).send('OK');
});

This endpoint at https://yourapp.com/api/webhooks/petstore receives all notifications.

Step 2: Create the Endpoint Handler

Build the server-side handler to process incoming webhooks.

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

app.post('/api/webhooks/petstore', (req, res) => {
  // 1. Verify signature
  const signature = req.headers['x-webhook-signature'];
  const payload = JSON.stringify(req.body);

  if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // 2. Extract event details
  const { event, data, timestamp } = req.body;

  console.log(`Received event: ${event}`);

  // 3. Process based on event type
  switch (event) {
    case 'order.created':
      handleNewOrder(data);
      break;
    case 'order.shipped':
      handleOrderShipped(data);
      break;
    case 'inventory.low':
      handleLowInventory(data);
      break;
    default:
      console.log(`Unknown event: ${event}`);
  }

  // 4. Return quickly
  res.status(200).send('OK');
});

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(`sha256=${expected}`)
  );
}

// Start server
app.listen(3000, () => console.log('Webhook server running'));

This handler verifies the signature, processes the event, and responds quickly.

Step 3: Register Your Webhook

Connect your endpoint to the Pet Store API. Use the API or dashboard.

Via API:

curl -X POST "https://api.petstoreapi.com/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/api/webhooks/petstore",
    "events": [
      "order.created",
      "order.shipped",
      "order.cancelled",
      "inventory.low"
    ],
    "active": true
  }'

Via Dashboard:

  1. Log into the Pet Store API dashboard
  2. Navigate to Webhooks settings
  3. Add your URL
  4. Select events to subscribe to
  5. Save configuration

Step 4: Test Your Implementation

Verify everything works before deploying to production.

Test with curl:

Send a test payload to your endpoint:

curl -X POST "https://yourapp.com/api/webhooks/petstore" \
  -H "Content-Type: application/json" \
  -H "x-webhook-signature: sha256=test" \
  -d '{
    "event": "test.event",
    "data": { "message": "Test webhook" },
    "timestamp": "2024-01-15T10:00:00Z"
  }'

Use the dashboard test feature:

Most APIs provide a test button. Click it to send a sample webhook. Check your logs for the incoming request.

Verify idempotency:

Send the same event twice. Your handler should process it only once:

const processedEvents = new Set();

app.post('/api/webhooks/petstore', (req, res) => {
  const eventId = req.body.eventId || req.body.timestamp;

  if (processedEvents.has(eventId)) {
    console.log(`Duplicate event: ${eventId}`);
    return res.status(200).send('Already processed');
  }

  processedEvents.add(eventId);
  // Process the event...
});

Step 5: Handle Failures Gracefully

Production webhooks need robust error handling.

Process asynchronously:

app.post('/api/webhooks/petstore', (req, res) => {
  // Return immediately
  res.status(200).send('OK');

  // Process in background
  queueWebhook(req.body).catch(err => {
    console.error('Failed to queue webhook:', err);
  });
});

Implement dead letter queue:

const webhookQueue = [];
const maxRetries = 3;
const retryDelay = 60000;

async function processWebhook(event) {
  try {
    await handleEvent(event);
  } catch (error) {
    event.retryCount = (event.retryCount || 0) + 1;

    if (event.retryCount < maxRetries) {
      // Retry after delay
      setTimeout(() => {
        processWebhook(event);
      }, retryDelay * event.retryCount);
    } else {
      // Move to dead letter
      await saveToDeadLetterQueue(event, error);
    }
  }
}

Monitor health:

let webhookSuccessCount = 0;
let webhookFailureCount = 0;

app.post('/api/webhooks/petstore', (req, res) => {
  res.status(200).send('OK');

  processWebhook(req.body)
    .then(() => webhookSuccessCount++)
    .catch(() => webhookFailureCount++);
});

// Expose metrics
app.get('/api/health/webhooks', (req, res) => {
  res.json({
    success: webhookSuccessCount,
    failure: webhookFailureCount,
    successRate: webhookSuccessCount / (webhookSuccessCount + webhookFailureCount)
  });
});

Step 6: Secure Your Endpoints

Production webhooks face attack attempts. Protect your endpoints.

Verify every signature:

app.post('/api/webhooks/petstore', (req, res) => {
  const signature = req.headers['x-webhook-signature'];

  if (!signature) {
    return res.status(401).send('Missing signature');
  }

  // Verify...
});

Limit by IP if supported:

Some services publish their IP ranges. Configure your firewall:

const ALLOWED_IPS = ['203.0.113.0', '198.51.100.0'];

app.use((req, res, next) => {
  const clientIp = req.ip;

  if (!ALLOWED_IPS.includes(clientIp)) {
    return res.status(403).send('Forbidden');
  }

  next();
});

Log everything:

app.post('/api/webhooks/petstore', (req, res) => {
  console.log('Webhook received:', {
    event: req.body.event,
    timestamp: new Date().toISOString(),
    ip: req.ip,
    userAgent: req.headers['user-agent']
  });

  res.status(200).send('OK');
});

Step 7: Deploy to Production

Move from development to production.

  1. Deploy your endpoint to a stable server
  2. Update webhook URL to your production URL
  3. Enable TLS with valid certificates
  4. Configure monitoring for failures
  5. Set up alerts for webhook errors
// Production webhook handler
app.post('/api/webhooks/petstore',
  express.json({ limit: '1mb' }),
  rateLimit({ windowMs: 60000, max: 100 }),
  (req, res) => {
    // Handle webhook
  }
);

Pet Store API Webhook Configuration

The Pet Store API makes webhook setup straightforward. Register your endpoint, choose events, and start receiving notifications.

Check docs.petstoreapi.com for the complete webhook API. You'll find event details, payload schemas, and signature verification steps.

Webhooks keep your systems synchronized without constant polling. Set them up once and receive updates automatically.