Delivery Guarantees

Hook Mesh provides an at-least-once delivery guarantee with a 48-hour delivery window. We ensure your webhooks reach their destination, even through temporary failures.

At-Least-Once Delivery

Every webhook job created in Hook Mesh is guaranteed to be delivered at least once (and possibly more than once due to retries). This means your webhook endpoints must be idempotent.

What "At-Least-Once" Means

✓ Guaranteed Delivery
If a webhook job is created and the endpoint is reachable, it WILL be delivered successfully (within 48 hours).
⚠ Possible Duplicates
The same webhook may be delivered multiple times if retries occur. Your endpoint must handle duplicates gracefully using idempotency keys.
ℹ Order Not Guaranteed
Webhooks may arrive out of order. Use timestamps or sequence numbers in your payload to handle ordering.

Idempotency Required

Since webhooks can be delivered more than once, your endpoint must be idempotent. Use the event_id field to detect and ignore duplicates.

48-Hour Delivery Window

Hook Mesh will attempt to deliver each webhook for up to 48 hours using exponential backoff retries. This is the longest delivery window in the industry.

Time WindowBehavior
0 - 4 hoursAggressive retries (6 attempts with exponential backoff)
4 - 48 hoursRetry every 6 hours until delivered or 48 hours elapsed
After 48 hoursJob marked as discarded (permanent failure)
Example: 48-Hour Timeline
T+0s:     Initial delivery → Failed (500 error)
T+5s:     Retry 1 → Failed
T+25s:    Retry 2 → Failed
T+2m:     Retry 3 → Failed
T+10m:    Retry 4 → Failed
T+50m:    Retry 5 → Failed
T+4h:     Retry 6 → Failed
T+10h:    Retry 7 → Failed
T+16h:    Retry 8 → Failed
T+22h:    Retry 9 → Failed
T+28h:    Retry 10 → Failed
T+34h:    Retry 11 → Failed
T+40h:    Retry 12 → Failed
T+46h:    Retry 13 → Failed
T+48h:    Final retry → Failed

          Job discarded (exceeded 48-hour window)

Industry Leading

Hook Mesh's 48-hour delivery window is 8x longer than Stripe (6h), 48x longer than Svix (1h), and 24x longer than Slack (2h). This ensures maximum delivery success even during extended outages.

How We Ensure Delivery

Hook Mesh uses multiple reliability mechanisms to guarantee webhook delivery.

1.

Persistent Job Queue

All webhook jobs are persisted to a durable database before delivery attempts. Jobs survive service restarts and infrastructure failures.

2.

Exponential Backoff Retries

Failed deliveries are automatically retried with exponential backoff (5s, 25s, 2m, 10m, 50m, 4h, then every 6h).

3.

Circuit Breaker

Consistently failing endpoints are paused to prevent wasted attempts. Jobs are queued and delivered when the endpoint recovers.

4.

Distributed Architecture

Webhook delivery service runs on multiple servers with automatic failover. No single point of failure.

5.

Monitoring & Alerting

Automatic health checks and alerts ensure delivery issues are detected and resolved quickly.

When Deliveries Are Discarded

Webhook jobs are only discarded (marked as failed permanently) in specific scenarios.

ScenarioActionReason
48 hours elapsedDiscardedExceeded maximum retry window
4xx error (except 429)DiscardedClient error (won't succeed on retry)
Endpoint deletedDiscardedDestination no longer exists
Application deletedDiscardedParent resource deleted
5xx errorRetriedTemporary server error
TimeoutRetriedNetwork issue
Connection errorRetriedEndpoint temporarily unreachable

Return Correct Status Codes

Always return 2xx if the webhook was processed successfully, even if there's a business logic error. Use 5xx only for server errors that should trigger retries.

Comparing Delivery Models

Different webhook providers offer different delivery guarantees.

GuaranteeDescriptionProvider Example
At-least-onceGuaranteed delivery, possible duplicatesHook Mesh, Stripe, Svix
Exactly-onceNo duplicates, but requires distributed transactions (expensive, slower)Rare (too expensive for webhooks)
At-most-onceNo duplicates, but delivery not guaranteedSimple webhook systems

Why At-Least-Once?

At-least-once is the industry standard for webhooks because it maximizes reliability without the cost and complexity of distributed transactions. Implementing idempotency on the consumer side is much simpler and cheaper than exactly-once delivery.

Best Practices for Consumers

Follow these guidelines to handle at-least-once delivery correctly.

Node.js - Idempotent Webhook Handler
import { db } from './database';

async function handleWebhook(req, res) {
  const { event_id, event_type, payload } = req.body;

  // 1. Check if we've already processed this webhook
  const existing = await db.query(
    'SELECT id FROM processed_webhooks WHERE event_id = $1',
    [event_id]
  );

  if (existing.rows.length > 0) {
    console.log(`Webhook ${event_id} already processed - ignoring duplicate`);
    return res.status(200).json({ status: 'already_processed' });
  }

  // 2. Process the webhook within a transaction
  try {
    await db.query('BEGIN');

    // Process the event
    if (event_type === 'user.created') {
      await db.query(
        'INSERT INTO users (id, email) VALUES ($1, $2)',
        [payload.user_id, payload.email]
      );
    }

    // 3. Mark as processed (prevents duplicates)
    await db.query(
      'INSERT INTO processed_webhooks (event_id, processed_at) VALUES ($1, NOW())',
      [event_id]
    );

    await db.query('COMMIT');

    // 4. Always return 2xx on success
    res.status(200).json({ status: 'processed' });
  } catch (error) {
    await db.query('ROLLBACK');

    // 5. Return 5xx for retryable errors
    console.error('Failed to process webhook:', error);
    res.status(500).json({ error: 'Processing failed' });
  }
}
  • Track processed webhooks - Store event_id in a database to detect duplicates
  • Use database transactions - Process webhook + mark as processed in a single atomic transaction
  • Return 2xx immediately - Respond quickly to avoid timeouts. Process asynchronously if needed.
  • Use timestamps for ordering - Don't assume webhooks arrive in order. Check timestamps to handle out-of-order delivery.
  • Clean up old records - Periodically delete processed_webhooks older than 30 days

Related Documentation