> For the complete documentation index, see [llms.txt](https://docs.enate.net/enate-help/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.enate.net/enate-help/integrations/enate-integrations/webhooks/webhook-delivery-and-failure-handling.md).

# Webhook Delivery and Failure Handling

This page covers what happens after an event fires in Enate: how the message reaches you, what Enate does when your endpoint doesn't accept it, and the behaviours you need to design your receiving endpoint around.

### How Delivery Works

When an event you've subscribed to occurs, Enate doesn't call your endpoint during the operation that caused it. It queues the message and delivers it separately, shortly afterwards. Saving a Case in Work Manager doesn't wait for your system to respond, and a slow or unavailable endpoint of yours never slows down or blocks work in Enate.

Delivery is a single HTTP POST to your `SubscriberURL`, carrying the `x-enate-signature` header and your optional custom header. See How a Webhook Message Is Sent for the full shape of the request.

Enate sends one POST per event per matching subscription. Two subscriptions that both match the same event produce two separate POSTs, each with its own signature and its own custom header.

### What Counts as Success

Enate treats any **2xx** response as success. Every other status code is a failure, and so is a connection that can't be established or a response that never arrives.

{% hint style="warning" %}
**A 4xx response doesn't stop the retries.** Enate makes no distinction between a 400, a 404, a 401 and a 500. Your endpoint can't reject an event by replying with a client error code, and doing so will have Enate send the same message again. If you want Enate to stop sending an event, unsubscribe. If you want to ignore a particular message, return a 2xx and discard it at your end.
{% endhint %}

Point your `SubscriberURL` at the endpoint that does the work, rather than at something that redirects to it. A redirect adds a round trip to every message, and some redirect responses change the request method, so the endpoint at the end of the chain may not receive the POST body you're expecting.

Enate doesn't set a timeout of its own on the call, so the platform default of around 100 seconds applies. An endpoint that takes longer than that to reply is treated as a failure and the message is retried, even if your system went on to process it successfully. Acknowledge the message quickly with a 2xx and do your processing afterwards, rather than holding the connection open while you work.

### Retries and Failed Deliveries

| Behaviour                                             | Detail                                                                           |
| ----------------------------------------------------- | -------------------------------------------------------------------------------- |
| Maximum delivery attempts                             | 10, including the first                                                          |
| What happens between attempts                         | The message goes back on the queue and Enate delivers it again                   |
| What happens after the tenth failed attempt           | The message is discarded. It isn't retried further and won't reach your endpoint |
| Notification of a message that failed all 10 attempts | None. Enate doesn't email you, raise a Work Item, or mark the subscription       |
| Effect on the subscription                            | None. It stays active and the next event is attempted normally                   |

{% hint style="warning" %}
**There's no self-service replay.** A message that fails all 10 attempts won't arrive, and there's nothing in the Enate UI or the API that lets you list failed messages or send them again. If your endpoint is down for an extended period, you'll have a gap in what you received and no way to detect it from Enate's side. Reconcile against the Enate APIs if completeness matters to your process, and raise a support ticket if you need to know what was in the gap.
{% endhint %}

#### Messages Enate Doesn't Attempt at All

Two situations produce no message, and no failure either:

* **The subscribing account has lost permission.** Enate re-checks the subscribing account's permission on the scoped object every time it prepares a message. If the account no longer has read access, that individual message is skipped silently. The subscription itself isn't deleted, so messages resume if the permission is restored.
* **The subscription no longer exists.** PacketUpdated subscriptions are deleted automatically when the work item closes, so nothing is sent for it after that point.

### Test Work Items Raise Webhooks Too

A work item running against a process version in Testing state raises exactly the same Webhook events as a live one. Enate doesn't suppress them, and the message body carries no field that tells you which kind you've received.

{% hint style="warning" %}
**You can't tell a test work item from a live one from the Webhook message itself.** The payload carries no test or live indicator, so a handler that acts on the message alone will treat both the same. Plan for this before you connect a Webhook to anything that acts on the data, such as creating records in a downstream system or notifying end customers.
{% endhint %}

The practical way to keep test traffic out of a downstream system is to scope your subscriptions so it never matches in the first place. Subscriptions are matched on the Company, Contract, Service or process definition you name when you subscribe, so running your process trials under a Contract or Service you haven't subscribed to keeps them off the Webhook entirely. If you can't separate them that way, keep the Webhook subscription pointed at a non production endpoint until the process goes live.

### Throughput

Enate delivers Webhook messages with limited concurrency per Enate instance, so a burst of events is delivered over a short period rather than all at once. The limit scales with the size of your Enate instance, and isn't something that can be set per customer. Treat the exact figure as subject to change, and design your endpoint so that it doesn't depend on one.

In practice this matters in two ways:

* A large batch operation, for example starting several hundred work items at once, produces a stream of messages spread over time rather than a simultaneous flood at your endpoint.
* Your endpoint is still the slower half of the exchange in most cases. An endpoint that takes several seconds per message will hold up the queue for that subscription, because the messages waiting behind it can't be delivered until the in-flight ones finish.

If you're integrating at high volume, accept quickly and process asynchronously. That keeps Enate's delivery moving and keeps you well clear of the timeout.

### Delivery Guarantees, Ordering and Duplicates

#### Expect duplicates

Delivery is at-least-once, so your endpoint has to tolerate receiving the same event more than once. The common causes:

* **Retries after a partial success.** If your endpoint processes a message and then fails to return a 2xx, for example it times out while replying, Enate counts that as a failure and sends the same message again.
* **Several updates in quick succession.** Each update to a work item is its own event, so a burst of changes produces a burst of near identical PacketUpdated messages.
* **One business event raising two Webhooks.** An incoming email linked to a Ticket raises NewCommunication for the email, and, if the email also changes the Ticket, a separate PacketUpdated for the Ticket. If you hold both subscriptions you receive both.
* **Overlapping subscriptions.** A SpecificBusinessObjectUpdated subscription and a BusinessObjectTypeUpdated subscription that both cover the same Company each produce their own message.

Make your handler idempotent. For NewCommunication, the `GUID` field identifies the communication and is a reliable deduplication key. For Packet events the `GUID` is the work item rather than the change, so it repeats across every update to that work item. If you need to detect repeats there, compare the payload you've just received against the last one you stored for that work item.

#### Don't rely on ordering

Messages are not guaranteed to arrive in the order the events happened, and a retry of an earlier message can land after a later one has already been delivered. Two consequences worth designing for:

* A PacketUpdated message can arrive carrying an older state than one you've already processed. Treat each message as a snapshot at the time it was produced rather than as a sequential change feed.
* If you need the authoritative current state of a work item, call the Enate API for it rather than assuming the last message you received is the latest.

### Worked Example: A Resilient Endpoint

A typical receiving endpoint for a NewCommunication subscription on Ticket 200243-T would:

1. Check the `x-enate-signature` header against the signature returned when the subscription was created, and reject the request with a 403 if it doesn't match.
2. Read the `GUID` from the body, for example `31fbfa65-dc39-4c9c-a1f4-47dfef636bea`.
3. Check whether that GUID has already been handled. If it has, return 200 and stop.
4. Write the message to your own queue or table.
5. Return 200.
6. Process the message from your own store, outside the request.

That order matters. Steps 4 and 5 are quick, so Enate gets its 2xx well inside the 100 second timeout, and step 3 means a retry costs you nothing. If step 6 fails you can retry it yourself, rather than depending on Enate to send the message again.

Note what the endpoint above does **not** do: it doesn't return a 4xx when it dislikes a message. A message it can't use is accepted with a 200 and discarded, because a 4xx would simply bring the same message back up to nine more times.

Other limits that apply across the platform, including the rate limits on Enate's APIs, are listed on the Maximum Limits in Enate page:

{% content-ref url="/pages/oPSb2TTA4ot02bdzLFLq" %}
[Maximum Limits in Enate](/enate-help/builder/builder/maximum-limits-in-enate.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.enate.net/enate-help/integrations/enate-integrations/webhooks/webhook-delivery-and-failure-handling.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
