Skip to main content
npayload is launching soon.
npayloadDocs
Concepts

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

TypeHow it worksUse case
Webhooknpayload POSTs the message to your HTTP endpointMicroservices, serverless functions, external APIs
QueueMessages are queued for pull-based consumptionBatch processing, worker pools
Consumer groupShared subscription with load balancing across consumersHigh-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:

AttemptDelay
1Immediate
21 second
330 seconds
45 minutes
530 minutes
62 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.

StateDescription
ClosedNormal operation. Messages are delivered.
OpenToo many consecutive failures. Deliveries are paused.
Half-openTesting 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

StateDescription
ActiveNormal operation. Messages are delivered.
PausedDelivery is paused. Messages are queued.
DisabledCircuit breaker has opened. Waiting for recovery.
ArchivedSoft-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

Was this page helpful?

On this page