Skip to main content
npayload is launching soon.
npayloadDocs
Use Cases

Webhook management

Reliable webhook delivery with retries, signature verification, circuit breaker, and DLQ

Webhooks are the most common way to deliver events to external systems. npayload turns unreliable HTTP calls into guaranteed delivery with automatic retries, circuit breakers, and a dead letter queue for failed deliveries.

The challenge

Building reliable webhook delivery is harder than it looks:

  • Retries: Endpoints go down. You need exponential backoff without overwhelming the target.
  • Signatures: Receivers need to verify that webhooks are authentic.
  • Circuit breaking: A failing endpoint should not consume all your retry budget.
  • Observability: You need to know which webhooks failed and why.
  • Replay: When an endpoint recovers, you need to replay missed webhooks.

How npayload solves it

Publish a message to a channel. npayload delivers it to every webhook subscriber with retries, signatures, and circuit breaking built in.

Set up webhook delivery

// Create a channel for your events
await npayload.channels.create({
  name: 'platform-events',
  description: 'Events from the platform API',
});

// Register a webhook subscriber
await npayload.subscriptions.create({
  channel: 'platform-events',
  name: 'customer-webhook',
  type: 'webhook',
  endpoint: {
    url: 'https://customer.example.com/webhooks/events',
    method: 'POST',
    headers: {
      'X-Webhook-Source': 'my-platform',
    },
  },
  delivery: {
    timeoutMs: 30000,
    retryPolicy: {
      maxAttempts: 6,
      initialDelayMs: 1000,
      maxDelayMs: 7200000, // 2 hours
      backoffMultiplier: 2,
    },
  },
});

Publish events

await npayload.messages.publish({
  channel: 'platform-events',
  routingKey: 'invoice.paid',
  payload: {
    event: 'invoice.paid',
    invoiceId: 'inv_001',
    amount: 299.00,
    currency: 'USD',
    paidAt: new Date().toISOString(),
  },
});

npayload delivers this to every webhook subscriber. Each delivery includes:

  • Signature header (x-npayload-signature) for verification
  • Retry on failure with exponential backoff
  • Unique delivery ID for deduplication
  • Timestamp for replay ordering

Signature verification (receiver side)

Webhook receivers should verify the signature before processing:

// In your webhook endpoint
app.post('/webhooks/events', (req, res) => {
  const isValid = npayload.webhooks.verify(
    req.body,
    req.headers['x-npayload-signature'],
    process.env.WEBHOOK_SECRET!
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the event
  handleEvent(req.body.payload);
  res.status(200).json({ received: true });
});

Retry behavior

npayload retries failed deliveries with exponential backoff:

AttemptDelayTotal elapsed
1Immediate0
21 second1s
330 seconds31s
45 minutes~5.5 min
530 minutes~35 min
62 hours~2.5 hours

A delivery is retried when:

  • The endpoint returns a 5xx status code
  • The request times out
  • The connection is refused

A delivery is not retried when:

  • The endpoint returns 2xx (success)
  • The endpoint returns 4xx (client error, except 429)
  • The endpoint returns 429 (rate limited, retried with the Retry-After header)

Circuit breaker

Every subscription has a built-in circuit breaker. If an endpoint fails repeatedly, the circuit opens and deliveries are paused to protect both sides.

StateBehavior
ClosedNormal delivery
OpenDeliveries paused, messages queued
Half-openTesting with a single delivery

When the endpoint recovers, the circuit breaker closes automatically and queued messages are delivered.

// Manually reset a circuit breaker if needed
await npayload.subscriptions.resetCircuit('sub_abc123');

Dead letter queue

After all retry attempts are exhausted, failed deliveries move to the DLQ. Nothing is lost.

// Inspect failed deliveries
const entries = await npayload.dlq.list({
  subscriptionId: 'sub_abc123',
  limit: 50,
});

for (const entry of entries.items) {
  console.log(`Failed: ${entry.messageId}`);
  console.log(`Reason: ${entry.failureReason}`);
  console.log(`Attempts: ${entry.attemptCount}`);
}

// Replay a single entry
await npayload.dlq.replay(entry.gid);

// Replay all entries for a subscription
await npayload.dlq.replayAll({ subscriptionId: 'sub_abc123' });

Multi-tenant webhook delivery

If you are building a platform that delivers webhooks to your customers, npayload scales to thousands of endpoints:

// Each customer gets their own subscription
for (const customer of customers) {
  await npayload.subscriptions.create({
    channel: 'platform-events',
    name: `webhook-${customer.id}`,
    type: 'webhook',
    filter: { routingKey: `customer.${customer.id}.*` },
    endpoint: {
      url: customer.webhookUrl,
    },
  });
}

Each customer's webhook has independent retries, circuit breaking, and DLQ.

Why npayload for webhooks

FeatureBenefit
Automatic retriesConfigurable backoff, up to days of retry
Circuit breakerProtect failing endpoints automatically
Signature verificationHMAC signatures on every delivery
Dead letter queueFailed deliveries never lost, always replayable
Per-subscriber isolationOne customer's failures do not affect others
Delivery trackingFull observability into every delivery attempt
Routing filtersDeliver only relevant events per subscriber

Next steps

Was this page helpful?

On this page