Subscriptions
Webhook, queue, and consumer group delivery with retries, circuit breakers, and DLQ
A subscription defines where and how messages from a channel are delivered. Each subscription receives every message independently, enabling fan-out to multiple consumers.
Subscription types
| Type | How it works | Use case |
|---|---|---|
| Webhook | npayload POSTs the message to your HTTP endpoint | Microservices, serverless functions, external APIs |
| Queue | Messages are queued for pull-based consumption | Batch processing, worker pools |
| Consumer group | Shared subscription with load balancing across consumers | High-throughput workloads with horizontal scaling |
Webhook subscriptions
The most common type. npayload sends an HTTP POST to your endpoint for every message.
const subscription = await npayload.subscriptions.create({
channel: 'orders',
name: 'fulfillment-service',
type: 'webhook',
endpoint: {
url: 'https://fulfillment.example.com/webhooks/orders',
method: 'POST',
headers: {
'X-Custom-Header': 'value',
},
},
});Queue subscriptions
Pull-based. Your consumers poll for messages at their own pace.
const subscription = await npayload.subscriptions.create({
channel: 'batch-jobs',
name: 'processor',
type: 'queue',
});
// Pull messages
const messages = await npayload.subscriptions.pull('processor', { limit: 10 });Consumer groups
Shared subscriptions with load balancing. Each message is delivered to exactly one consumer in the group, distributing the workload.
const group = await npayload.consumerGroups.create({
channel: 'tasks',
name: 'task-workers',
consumers: ['worker-1', 'worker-2', 'worker-3'],
});Routing key filters
Filter messages by routing key using glob patterns. Only matching messages are delivered to the subscription.
await npayload.subscriptions.create({
channel: 'events',
name: 'order-handler',
type: 'webhook',
endpoint: { url: 'https://example.com/orders' },
filter: {
routingKey: 'order.*', // Matches order.created, order.paid, etc.
},
});Delivery and retries
npayload retries failed deliveries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 second |
| 3 | 30 seconds |
| 4 | 5 minutes |
| 5 | 30 minutes |
| 6 | 2 hours |
After all retry attempts are exhausted, the delivery is moved to the dead letter queue (DLQ).
Custom retry policy
await npayload.subscriptions.create({
channel: 'payments',
name: 'billing-service',
type: 'webhook',
endpoint: { url: 'https://billing.example.com/webhook' },
delivery: {
timeoutMs: 30000,
retryPolicy: {
maxAttempts: 5,
initialDelayMs: 1000,
maxDelayMs: 60000,
backoffMultiplier: 2,
},
},
});Circuit breaker
Every subscription has a built-in circuit breaker that protects both your endpoint and npayload from cascading failures.
| State | Description |
|---|---|
| Closed | Normal operation. Messages are delivered. |
| Open | Too many consecutive failures. Deliveries are paused. |
| Half-open | Testing if the endpoint has recovered. |
When the circuit breaker opens, new deliveries are queued. Once the endpoint recovers (responds with 2xx), the circuit breaker closes and queued deliveries resume.
// Reset manually if needed
await npayload.subscriptions.resetCircuit('sub_abc123');Dead letter queue (DLQ)
Messages that fail all retry attempts are moved to the DLQ. You can inspect, debug, and replay them.
// List DLQ entries
const entries = await npayload.dlq.list({ subscriptionId: 'sub_abc123' });
// Replay a failed delivery
await npayload.dlq.replay('dlq_entry_123');
// Replay all entries
await npayload.dlq.replayAll({ subscriptionId: 'sub_abc123' });npayload never drops a message. If delivery fails, it goes to the DLQ. You can replay it at any time within the retention window.
Webhook signature verification
Every webhook delivery includes an HMAC signature in the x-npayload-signature header. Always verify before processing.
app.post('/webhooks/orders', (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 message
res.status(200).json({ received: true });
});Subscription lifecycle
| State | Description |
|---|---|
| Active | Normal operation. Messages are delivered. |
| Paused | Delivery is paused. Messages are queued. |
| Disabled | Circuit breaker has opened. Waiting for recovery. |
| Archived | Soft-deleted. No delivery. |
// Pause delivery
await npayload.subscriptions.pause('sub_abc123');
// Resume delivery
await npayload.subscriptions.resume('sub_abc123');
// Rotate webhook secret
await npayload.subscriptions.rotateSecret('sub_abc123');Next steps
- Channels to understand message streams
- Messages to understand what is delivered
- Webhooks guide for a complete webhook integration walkthrough
- API reference for the full subscriptions API
Was this page helpful?