# Understanding the Webhook Retry Mechanism with maxRetries in OpenWA

> Learn how OpenWA's webhook retry mechanism with maxRetries ensures reliable delivery. Explore automatic resends, exponential back-off, and database persistence for robust integrations.

- Repository: [Yudhi Armyndharis/OpenWA](https://github.com/rmyndharis/OpenWA)
- Tags: deep-dive
- Published: 2026-05-21

---

**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`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/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.

```typescript
// 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.

```typescript
// 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`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/src/config/configuration.ts). This allows different deployments to adjust resilience policies based on endpoint reliability requirements.

```typescript
// 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`](https://github.com/rmyndharis/OpenWA/blob/main/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.

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

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

```

## Summary

- The **maxRetries** mechanism in OpenWA defaults to 5 attempts but is configurable via [`src/config/configuration.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/config/configuration.ts).
- Failed deliveries are stored in the database via the `Webhook` entity in [`src/modules/webhook/entities/webhook.entity.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/webhook/entities/webhook.entity.ts), which tracks the `retries` count.
- The `WebhookService` in [`src/modules/webhook/webhook.service.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/webhook/webhook.service.ts) handles delivery logic, error catching, and retry incrementing.
- A background processor registered in [`src/modules/webhook/webhook.module.ts`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/webhook/webhook.module.ts) periodically scans for pending webhooks and reattempts delivery using exponential back-off.
- The system uses `axios` for HTTP transport and NestJS's HTTP module for integration.

## 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`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/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`](https://github.com/rmyndharis/OpenWA/blob/main/src/modules/webhook/entities/webhook.entity.ts). This persistent storage ensures that retry state is maintained across application restarts and system failures.