Webhooks Configuration

Overview

Cleeng webhooks are real-time HTTP POST requests sent to the endpoint(s) you provide, so your systems can be informed by Cleeng when specific events happen in the platform. This page walks you through webhook subscription management and delivery via Webhooks API.

Use this API to subscribe your HTTP endpoints to webhook topics. When specific events occur (for example, a new customer registered or a subscription was created ), the system delivers webhook payloads to all subscribed endpoints with optional HMAC signature verification and configurable retry policies.

Webhooks API enables you to:

Main concepts

  • Endpoint - An individually addressable object for receiving webhook notifications. It contains properties like "name" and "URL", and can be independently created, updated, or deleted.
  • Event type (topic) - a notification that you can subscribe to. When a specific event occurs, the system sends a notification to the webhook endpoints that are subscribed to that event type. (For a list of available events (topics), see Webhook Topics section).
  • Signing secret - a unique, server-generated key used to create a cryptographic signature for each webhook notification sent. This signature is created using the HMAC-SHA256 hashing algorithm:
    • Automatic Generation: The signing secret is automatically generated by Cleeng's server when you create a new webhook endpoint. This simplifies the setup process and improves security.
    • Rotation: The API includes a specific function that allows you to rotate your signing secret, generating a new one on demand for enhanced security.

    🚧

    Important

    The old secret is immediately invalidated when rotated, ensuring that a compromised secret cannot be used by malicious third party the moment you rotate. For routine rotation, we recommend scheduling during low-traffic hours and updating the secret in your receiving application immediately after rotation to ensure uninterrupted webhook delivery.

Setting up and securing your webhooks

This example use case will guide you through the process of configuring a webhook endpoint, subscribing it to specific events (topics), and verifying the authenticity of the notifications you receive.

Step 1: Create a New Webhook Endpoint

First, register the URL where you want to receive notifications, and what event types this endpoint is subscribed to. You do this by making a POST request to the /endpoints resource.

Request:

POST /endpoints
Content-Type: application/json

{
  "url": "https://example.com/webhooks",
  "name": "Production handler",
  "events": [
     "customerRegistered"
     ],
}

The API will create the endpoint and return its details, including a unique id and your signingSecret.

Response:

{
  "id": "ep_123456789",
  "url": "https://example.com/webhooks",
  "name": "Production handler",
  "status": "active",
  "events": [
       "customerRegistered"
       ],
  "signing": {
      "algorithm":"HMAC-SHA256",
      "secret": "whsec_F6jdY+gjjYjZX7EIKQ3WU55Eu4OjjWxVXyIiI4XD8zQ="
      "secretPrefix":"whsec_F6"
      },
  "retry": {
    "maxAttempts": 4
  },
"metadata": {},
  "createdAt": "2026-03-16T10:17:49.250Z",
  "updatedAt": "2026-03-16T10:17:49.250Z"
}
🚧

Important

The signing secret is strictly confidential. It should not be divulged to any third party. Store it securely as you will need it to verify that incoming webhooks are genuinely from Cleeng.

Your endpoint is now active and will receive a POST request at https://example.com/webhooks (note that this is an example) whenever a customer is registered.

Step 2: Verify Webhook Signatures

To ensure incoming notifications are authentic, you must verify their signature. This is the most critical security step. Each webhook will include a header containing the signature.

Here is a pseudo-code example demonstrating how to implement the verification logic on your server.

function verifyWebhookSignature(request, signingSecret):
    // 1. Get the signature from the request header
    receivedSignature = request.headers["X-Webhook-Signature"]
   
    if receivedSignature is missing:
        reject request (401 Unauthorized)

    // 2. Get the raw request body (do NOT parse then re-serialize —
    //    use the raw bytes exactly as received)
    rawBody = request.rawBody

    // 3. Compute the expected signature
    expectedSignature = Base64( HMAC-SHA256( signingSecret, rawBody ) )

    // 4. Compare using a constant-time comparison to prevent timing attacks
    if NOT constantTimeEqual(receivedSignature, expectedSignature):
        reject request (401 Unauthorized)

    // 5. Signature is valid — process the webhook
    payload = JSON.parse(rawBody)
    handle(payload)

Here is Node.JS example:

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, headers, signingSecret) {
  // 1. Get the signature from the request header
  const signature = headers['X-Webhook-Signature'];
  if (!signature) {
    throw new Error('Missing X-Webhook-Signature header');
  }

  // 2. Compute the expected signature using HMAC-SHA256
  //    Use the raw request body exactly as received — do not parse
  //    and re-serialize, as any change in whitespace or key order
  //    will produce a different signature
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(rawBody)
    .digest('base64');

  // 3. Compare using constant-time comparison to prevent timing attacks
  const a = Buffer.from(signature);
  const b = Buffer.from(expected);

  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    throw new Error('Invalid webhook signature');
  }

  // 4. Signature is valid — safe to parse and process
  return JSON.parse(rawBody);
}

Configure Webhook Retries

To improve delivery reliability, you can configure your webhook endpoints to automatically retry delivery in the event of a transient failure (such as a 5xx error, timeout, or network issue). This section will guide you through enabling and managing this feature.

When enabled, failed deliveries are automatically retried up to a specified number of times using an exponential backoff strategy. This approach increases the delay between each subsequent attempt, giving your consumer's server time to recover.

The retry schedule is as follows:

AttemptDelay After Failure
1Immediate
2~10 seconds delay
3~20 seconds delay
4~40 seconds delay

Enable the Retry Policy

To enable retries, add the retryPolicy object to your webhook endpoint configuration.

{
 "retryPolicy": {  
    "enabled": true,  
    "maxAttempts": 4
 }
}
  • enabled: Set to true to activate the retry mechanism.
  • maxAttempts: Specify the total number of delivery attempts (1 initial + 3 retries in the example above). Maximum number: 4.

If the retryPolicy object is not included in your configuration, the endpoint will default to the standard single-delivery attempt.

Ensure Idempotent Processing

A retried request can sometimes result in the same event being delivered more than once. To prevent duplicate processing, your endpoint must be idempotent.

All webhook deliveries now include headers to help you handle potential duplicates:

HeaderDescription
X-Webhook-Delivery-IdA stable UUID for the delivery (same across retries)
X-Webhook-AttemptThe current attempt number (1-based)
X-Webhook-Max-AttemptsThe maximum number of attempts configured

Best Practice

When enabling retries, you should implement idempotency using the X-Webhook-Delivery-Id header, as duplicate deliveries may occur if an endpoint returns 5xx but still processes the request.

A delivery is considered permanently failed if it exhausts all retry attempts or if your endpoint returns a 4xx status code.

Example of Webhook Retries Configuration

See the example for PUT https://api.sandbox.cleeng.com/3.1/webhook_subscriptions/customerRegistered:

[
    {
        "url": "https:/test.example.com/webhook-handler/customer-registered",
        "retryPolicy": {  
            "enabled": true,  
            "maxAttempts": 4
        }
    }
]