Understanding the Webhook Retry Mechanism with maxRetries in OpenWA

The OpenWA webhook retry mechanism automatically resends failed webhook deliveries up to a configurable maximum attempt count (default 5) using exponential back-off, persisting retry state in the database and processing retries via a background task queue.

The rmyndharis/OpenWA repository implements a resilient webhook delivery system that ensures critical events reach external endpoints even during temporary network failures or service outages. Unlike simple fire-and-forget implementations, OpenWA persists failed webhook attempts and automatically retries them according to a configurable policy defined by the maxRetries setting.

How the Retry Queue Works

When a webhook fails to deliver, OpenWA does not discard the payload. Instead, the system stores the event in the database with an incremental retry counter and schedules it for future attempts. This queue-based approach ensures that temporary endpoint failures do not result in data loss.

The retry lifecycle follows these steps:

  1. A webhook event is triggered and the WebhookService attempts immediate delivery via HTTP POST.
  2. If the endpoint returns a non-2xx status or the request times out, the service catches the error and increments the retries field on the Webhook entity.
  3. The service compares the current retry count against the MAX_RETRIES constant defined in src/modules/webhook/webhook.service.ts.
  4. If the count is below the maximum, the webhook remains in PENDING status for the background processor to retry later.
  5. Once the retry count reaches MAX_RETRIES, the webhook is marked as failed and removed from the active queue.

Core Implementation in WebhookService

The WebhookService class in src/modules/webhook/webhook.service.ts contains the central logic for delivery attempts and retry management. This service uses axios via NestJS's HttpService to execute HTTP requests and handles failures through structured exception handling.

The MAX_RETRIES Constant

The maximum retry threshold is defined as a constant at the top of the service file. By default, this value is set to 5, meaning OpenWA attempts delivery once initially and retries up to four additional times.

// src/modules/webhook/webhook.service.ts
const MAX_RETRIES = 5;
const BASE_DELAY = 1000; // 1 second base for exponential back-off

This constant is compared against the retries column stored in the database entity during every failure event.

The send() Method and Error Handling

The send() method wraps the HTTP delivery logic in a try-catch block. When an exception occurs or the response status falls outside the 200-299 range, the method increments the retry counter and persists the updated entity.

// Conceptual implementation based on webhook.service.ts
async send(webhook: WebhookEntity): Promise<void> {
  try {
    const response = await this.httpService.post(
      webhook.url, 
      webhook.payload
    ).toPromise();
    
    if (response.status >= 200 && response.status < 300) {
      webhook.status = 'DELIVERED';
    } else {
      throw new Error(`Non-2xx status: ${response.status}`);
    }
  } catch (error) {
    webhook.retries += 1;
    
    if (webhook.retries >= MAX_RETRIES) {
      webhook.status = 'FAILED';
    } else {
      webhook.status = 'PENDING';
    }
    
    await this.webhookRepository.save(webhook);
  }
}

Database Schema and Retry Tracking

Retry state is maintained in the Webhook entity defined in src/modules/webhook/entities/webhook.entity.ts. This entity includes a retries integer column that tracks the number of delivery attempts made for each specific webhook payload.

The database persistence ensures that retry attempts survive application restarts. The WebhookProcessor—registered in src/modules/webhook/webhook.module.ts—queries for all records with status = 'PENDING' and retries < MAX_RETRIES on a configurable interval (defaulting to 30 seconds).

Configuration and Customization

You can override the default retry limit through the application configuration file located at src/config/configuration.ts. This allows different deployments to adjust resilience policies based on endpoint reliability requirements.

// src/config/configuration.ts
export default () => ({
  webhook: {
    maxRetries: 10,          // Override default of 5
    retryBaseDelayMs: 5000,  // Base delay for exponential back-off
  },
});

When the configuration loads, the WebhookService uses these values instead of the hardcoded defaults, enabling environment-specific retry policies without code changes.

Back-Off Strategy and Scheduling

OpenWA implements exponential back-off to prevent overwhelming failing endpoints with immediate retry storms. The delay before the next attempt calculates as BASE_DELAY * 2^retries, creating progressively longer intervals between attempts.

The background task responsible for executing retries is defined in src/modules/webhook/webhook.module.ts and typically runs every 30 seconds. This processor fetches pending webhooks and invokes the send() method again, respecting the back-off timing calculated from each entity's retry count.

// Example: Inspecting pending retries
const pendingWebhooks = await webhookService.findPending();

pendingWebhooks.forEach(webhook => {
  console.log(
    `Webhook ${webhook.id}: ${webhook.retries} of ${MAX_RETRIES} attempts`
  );
});

Summary

Frequently Asked Questions

What is the default maxRetries value in OpenWA?

The default value is 5, defined as the MAX_RETRIES constant in src/modules/webhook/webhook.service.ts. This means OpenWA attempts delivery once initially and retries up to four additional times before marking the webhook as permanently failed.

How does OpenWA handle webhook delivery failures?

When a delivery fails—either through a non-2xx HTTP response or a network timeout—the WebhookService catches the exception, increments the retries counter on the database entity, and either queues the webhook for another attempt or marks it as failed if the maximum retries have been reached.

Can I customize the retry delay interval?

Yes, the retry delay is configurable through src/config/configuration.ts using the retryBaseDelayMs property. OpenWA calculates actual delay times using exponential back-off based on this base value multiplied by 2 raised to the power of the current retry count.

Where is the retry count stored in OpenWA?

The retry count is stored in the retries column of the Webhook entity, defined in src/modules/webhook/entities/webhook.entity.ts. This persistent storage ensures that retry state is maintained across application restarts and system failures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →