Our platform has a seamless integration of optional idempotent requests for POST and PATCH operations. This integration closely adheres to the Idempotency-key ietf draft. These idempotent operations deliver the same result regardless of the number of requests made, using an idempotent key.

Leveraging this feature guarantees the safety and reliability of your requests, especially in cases where network or communication issues might inject uncertainty into request outcomes. Idempotent requests are critical for workflows that require the insurance of only one successful delivery request.

This feature was introduced in version 0.22.0 and is fully operable from 0.24.0 and beyond. Currently, the Idempotency headers are not enabled on the cloud platform but are available for self-hosting.

Please be aware that idempotency might not be supported in all community SDKs.

How to Perform an Idempotent Request with SDKs

Supporting SDKS will implement Idempotency for you as long as you have retries turned on. Not only is this seamless but it also ensures that no event request is dropped provided that your system stays up.

Idempotency is only implemented for /events/trigger endpoint.

Idempotent request with api

To make an idempotent request, simply include the Idempotency-Key: <key> in your request header. This key should be a unique client-generated value, boasting enough entropy to prevent collisions; we recommend a collision-resistant Unique Identifier, such as UUIDv7, CUID, or ULID, as your idempotency keys.

Understanding Response Replay

Our API stores the status code and response body from your initial idempotent request. This way if an identical idempotency key appears in a later request, the API will replay the saved response from the original request - even if it involves client or server error responses.

In the event of a response replay, the response will carry an Idempotency-Replay: true header.

Idempotent Request Responces

Here are some unique pieces constraints you should be aware of especially if you are not using the sdk:

  1. A <key> exceeding 255 characters will trigger a 400 Bad Request error.
  2. An inconsistent request body compared to the initial request will return a 422 Unprocessable Entity error.
  3. A ongoing processing of the initial request that hasn’t yet responded will result in a 409 Conflict error. This will include a Retry-After header.

Results will not saved if an API request is invalid.

Expiring Keys

All idempotency keys are ensured to be automatically removed once they are 24 hours and 1 minute old. After this period, the reused key will be treated as a new request and response.

Practical Examples

Example 1 - No idempotency

import { Novu } from '@novu/node';

const novu = new Novu("<NOVU_SECRET_KEY>");

await novu.trigger("<WORKFLOW_TRIGGER_IDENTIFIER>", {
  to: {
    subscriberId: "<UNIQUE_SUBSCRIBER_IDENTIFIER>",
  },
  payload: {
    name: "Hello World",
  },
  actor: "actorId",
  tenant: "tenantIdentifier",
});

Example 2 - Idempotency Active

import { Novu } from '@novu/node';

const novu = new Novu("<NOVU_SECRET_KEY>", {
    retryConfig: {
        retryMax: 5
    }
});

// This request is now idempotent
await novu.trigger("<WORKFLOW_TRIGGER_IDENTIFIER>", {
  to: {
    subscriberId: "<UNIQUE_SUBSCRIBER_IDENTIFIER>",
  },
  payload: {
    name: "Hello World",
  },
  actor: "actorId",
  tenant: "tenantIdentifier",
});

References