# Understanding webhook

**DRAFT — not yet available.** This page describes a feature whose development has not started
yet. Everything below — field names, payload structure, headers and behaviour — may change
before release, and the functionality is not available in any environment. Do not implement
against it. This page will lose the DRAFT notice when the feature ships; the release it belongs
to will be listed in the [Release Notes](/products/leandisposable/enterprise-documentation/developer-documentation/release-notes).

The document preservation webhook allows you to programmatically learn when a document you uploaded
through Lean Disposable has been successfully preserved (*conservazione a norma*).

Preservation is asynchronous: the document is accepted by the [`uploadDoc`](/products/leandisposable/enterprise-documentation/developer-documentation/api-references/lean-documents/lean-uploaddoc-method)
method, and a separate batch process submits it to the preservation system and later confirms
completion. Without a webhook there is no way for your application to know when that happens.

To receive the notification, add the optional `webhookUrl` property to the `uploadDoc` request.
Each uploaded document can be linked to a single [webhook configuration](/products/leandisposable/enterprise-documentation/developer-documentation/wb/wb-configuration).

Example:


```json
{
  "idOtp": 2587763,
  "deviceCode": "PROVIDER1",
  "externalKey": "CONTRACT-0001",
  "file": "encoded base 64 file",
  "extension": "pdf",
  "typeDoc": "CONTRACT",
  "webhookUrl": "https://example.com/callback"
}
```

Check the [webhook configuration](/products/leandisposable/enterprise-documentation/developer-documentation/wb/wb-configuration) page for the URL requirements and the
signature verification procedure.

When the document is accepted, the `uploadDoc` API returns `200` (`OK`). The response is unchanged
by this feature — supplying `webhookUrl` adds no field to it:


```json
{
  "deviceCode": "PROVIDER1",
  "typeDoc": "CONTRACT",
  "externalKey": "CONTRACT-0001",
  "uploadDate": 1733496805136,
  "filename": "3333217_a4ece4b3e9626ccfd3bac2eb77c0c1d7950be77e190a64fcf1957279e677ffc7.pdf"
}
```

Correlate the later notification using the `deviceCode` you sent in the request, together with the
`externalKey` if you supplied one. Both are echoed back in the notification payload.

No webhook notification is sent when the document is uploaded. The only notification is the one
emitted when preservation completes.

**Only successful preservation is notified — a `FAILED` state is never sent.** No webhook is
emitted while the document is still being processed, nor if a preservation attempt fails.

You do not need to handle preservation failures: when an attempt fails, Namirial takes the
document in charge and works on it until it reaches the `PRESERVED` state, at which point the
`document.preserved` notification is sent as usual. A failure therefore delays the notification,
it does not cancel it.

The practical consequence is that the absence of a notification always means *not preserved
yet* — either still in progress or being remediated on our side — and never *give up and stop
waiting*.

If `webhookUrl` is omitted, no notification is ever produced for that document. Documents uploaded
before this feature was released are not notified retroactively.

### Preservation notification

The system sends one notification when the document reaches the preserved state.


```mermaid
sequenceDiagram
    participant App as Your Application
    participant Lean as Lean Disposable
    participant Batch as Preservation Batch
    participant CAN as Preservation System

    App->>Lean: uploadDoc (webhookUrl)
    Lean-->>App: HTTP Status = 200
    Note over Lean,App: Document stored, preservation pending

    Lean->>Batch: document queued for preservation
    Batch->>CAN: submit document
    CAN-->>Batch: preservation completed
    Batch-->>App: Document Preserved Webhook
    App-->>Batch: HTTP Status = 200
```

The system sends the notification as an HTTP `POST` request to your configured webhook URL.

Example:


```json
{
  "eventId": "0199a3f2-7c4e-7a1b-9d33-2f8c1e5b4a70",
  "eventType": "document.preserved",
  "occurredAt": "2026-08-17T09:12:44Z",
  "externalKey": "CONTRACT-0001",
  "deviceCode": "PROVIDER1",
  "typeDoc": "CONTRACT",
  "submissionId": "SUB-0000123",
  "srId": "SR-0000456",
  "aipId": "AIP-0000789",
  "completedDate": "2026-08-17T09:10:02Z"
}
```

The webhook contains the following info:

* **eventId**: Unique identifier of the notification. Use it as the idempotency key.
* **eventType**: Event type — always `document.preserved`.
* **occurredAt**: Date and time the notification was produced, in ISO 8601 format (e.g. `2026-08-17T09:12:44Z`).
* **externalKey**: The external reference you supplied in the `uploadDoc` request, if any. Use it,
together with `deviceCode`, to correlate the notification with your upload.
* **deviceCode**: The device code of the disposable the document is linked to, exactly as supplied
in the `uploadDoc` request.
* **typeDoc**: Document type, as supplied at upload. Possible values: `IDENTIFICATION_DOC`,
`CONTRACT`, `AUDIT_LOG`, `IDENTITY_ASSERTION`.
* **submissionId**: Identifier of the submission to the preservation system.
* **srId**: Identifier of the preservation report (*rapporto di versamento*).
* **aipId**: Identifier of the archival information package (AIP).
* **completedDate**: Date and time preservation completed, in ISO 8601 format.


The payload never carries document content, credentials, or any authentication factor.

### Webhook Retry Mechanism

**Expected Response:**

- HTTP status code: `200` (OK)
- Response timeout: 10 seconds
- Response body: Not required (ignored if present)


**Only `200` is treated as success.** A response of `202` or `204` — natural for asynchronous
processing on your side — is treated as a failure and the notification is retried for the full
24-hour window. Answer `200` as soon as you have accepted the event, and process it afterwards.

**Retry Behavior:**

The system retries failed webhook deliveries using exponential backoff:

| Attempt | Wait Time | Cumulative Time |
|  --- | --- | --- |
| 1 | 1 second | 1 second |
| 2 | 2 seconds | 3 seconds |
| 3 | 4 seconds | 7 seconds |
| 4 | 8 seconds | 15 seconds |
| ... | ... | ... |
| Final | - | 24 hours max |


**Retry Triggers:**

- HTTP status codes: 4xx (except 400, 401, 403), 5xx
- Any 2xx other than 200, and any 3xx
- Network errors (timeout, connection refused, DNS failure)
- No response within 10 seconds


**Not Retried:**

- HTTP `400`, `401`, `403` — the payload or the credentials were rejected, retrying changes nothing
- The destination host is no longer allowlisted


Redirects are never followed: a `3xx` response is a failed delivery.

**After 24 Hours:**

- Retries stop permanently
- Notification is marked as failed
- No further attempts are made


**Handling Duplicate Notifications**

Delivery is at-least-once: the same notification may be delivered more than once due to retries or
network issues. Use the `eventId` field to detect and ignore duplicates:


```json
{
  "eventId": "0199a3f2-7c4e-7a1b-9d33-2f8c1e5b4a70",  // Use this as idempotency key
  "eventType": "document.preserved",
  ...
}
```

### Status Transition Diagram

The diagram below shows the preservation states of an uploaded document and the single transition
for which Lean Disposable sends a notification. `FAILED` is an internal state: it is never notified,
and Namirial remediates it until the document reaches `PRESERVED`.


```mermaid
flowchart LR
    Start([uploadDoc]) --> PENDING[PENDING]

    PENDING --> SUBMITTED[SUBMITTED]

    SUBMITTED --> D1{Preservation<br/>Succeeds?}
    D1 -->|YES| PRESERVED[PRESERVED]
    D1 -->|NO| FAILED[FAILED]

    FAILED -.->|Namirial remediation<br/>no webhook sent| SUBMITTED

    PRESERVED --> WH[/document.preserved<br/>webhook sent/]

    style PENDING fill:#87CEEB,stroke:#333,stroke-width:2px
    style SUBMITTED fill:#87CEEB,stroke:#333,stroke-width:2px
    style PRESERVED fill:#90EE90,stroke:#333,stroke-width:2px
    style FAILED fill:#87CEEB,stroke:#333,stroke-width:2px
```

**Notification Timing Limitations**.

The notification is emitted as soon as the preservation system confirms completion, but the time
between upload and confirmation is not bounded by an SLA. Factors affecting it include:

- The batch submission cadence
- Processing time of the preservation system
- Remediation of a failed preservation attempt on the Namirial side
- Retry attempts on your endpoint