---
title: Ingestion webhook
description: Spingi lead, deal e commenti dal tuo sistema verso Audin con un singolo endpoint tipizzato e idempotente — upsert per external_id, ereditando automazioni e assegnazioni.
---

L'**ingestion webhook** è un singolo endpoint che riceve eventi tipizzati dal
tuo sistema (`lead/created`, `deal/updated`, …) e li scrive nel CRM Audin,
ereditando gli stessi effetti delle scritture native: automazioni (workflow),
assegnazione del lead, storico delle modifiche. Ogni evento porta un tuo
identificativo (`external_id`) con cui Audin fa **upsert**: lo stesso record nel
tuo sistema corrisponde sempre allo stesso record in Audin.

## Quando usarlo

| Strumento | Modello | Quando |
|-----------|---------|--------|
| **Ingestion webhook** | Tu **spingi** eventi tipizzati (push) | Hai già un sistema sorgente (gestionale, form, altro CRM) e vuoi mantenerlo allineato ad Audin per `external_id`, lasciando ad Audin il match e gli effetti collaterali. |
| **API REST** | Tu **chiami** le risorse (`/leads`, `/deals`, …) | Vuoi controllo fine sulle singole risorse, leggere e scrivere con gli `id` Audin, orchestrare flussi complessi lato tuo. |
| **Datasource** | Audin **importa** in bulk | Carichi periodicamente liste/file (es. import CSV) senza una semantica evento-per-evento. |

L'ingestion webhook è la scelta giusta quando la **fonte di verità è il tuo
sistema** e vuoi un canale unico, idempotente e tipizzato per riversare i
cambiamenti in Audin.

<Callout type="info">
  Ogni chiamata all'ingestion webhook viene **registrata** e resta consultabile
  nella sezione **Sviluppatori** del dashboard: payload ricevuto, esito,
  eventuale errore e record Audin risultante. È il posto dove diagnosticare un
  evento rifiutato.
</Callout>

## Autenticazione

L'ingestion webhook richiede **due fattori**, entrambi appartenenti allo
**stesso Account**:

1. Un **token segreto per-Account** nell'URL — l'endpoint è
   `POST https://api.audin.ai/webhooks/ingest/<token>`, dove `<token>` (formato
   `aud_ing_…`) è una credenziale dedicata all'ingestion.
2. L'**Account API Key** nell'header `X-API-Key` (la stessa usata da tutta
   l'API REST partner, vedi [Autenticazione](/docs/api-rest/autenticazione)).

Se il token e l'API Key non si riferiscono allo stesso Account — o il token è
mancante/errato — la richiesta viene rifiutata con `403 webhook_auth_failed`.

Le scritture che richiedono un riferimento a un utente (es. `createdBy`,
`ownerId`) sono risolte automaticamente sull'owner dell'Account.

```bash
curl -X POST https://api.audin.ai/webhooks/ingest/<token> \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "id": "evt_001", "type": "lead/created", "data": { "external_id": "L-42" } }'
```

### Il token di ingestion

Il token (`aud_ing_…`) è un **segreto**: chi lo possiede insieme all'API Key può
scrivere nel tuo CRM. Trattalo come una credenziale — non condividerlo, non
inserirlo in URL pubblici o log, conservalo in modo sicuro lato server.

- **Ottenerlo / consultarlo:** dalla pagina **Sviluppatori → Webhook di
  ingestion** del dashboard. L'**URL completo** (token incluso) è ora
  **sempre visibile e copiabile** da quella pagina: non è più mostrato una sola
  volta alla generazione, puoi recuperarlo in qualsiasi momento. Resta comunque
  un segreto: trattalo come una credenziale.
- **Rotazione:** rigenerare il token **invalida immediatamente l'URL
  precedente**. Aggiorna il sistema sorgente con il nuovo URL prima — o subito
  dopo — la rigenerazione per evitare interruzioni.

## Envelope dell'evento

Ogni richiesta è un **singolo evento** (non un batch) con questa struttura:

| Campo | Tipo | Obbligatorio | Note |
|-------|------|:---:|------|
| `id` | string | sì | Identificativo dell'evento **lato tuo sistema**. È la **chiave di idempotency**: ripetere un `id` già processato non riesegue l'azione. |
| `type` | enum | sì | Tipo dell'evento (vedi tabella sotto). |
| `occurred_at` | string (ISO 8601) | no | Istante in cui l'evento è avvenuto nel tuo sistema. Solo per audit. |
| `suppress_triggers` | boolean | no | Se `true`, salta automazioni e assegnazioni (vedi [Backfill](#backfill)). Default `false`. |
| `data` | object | sì | Payload specifico per `type` (schema sotto). |

```json
{
  "id": "evt_8f3a",
  "type": "lead/updated",
  "occurred_at": "2026-06-16T10:00:00Z",
  "suppress_triggers": false,
  "data": {
    "external_id": "L-42",
    "email": "mario@acme.example.com"
  }
}
```

<Callout type="info">
  In alternativa al campo `suppress_triggers` puoi usare l'header
  `X-Suppress-Triggers: true`. I due meccanismi sono equivalenti: se uno dei due
  è attivo, le automazioni vengono saltate.
</Callout>

## Tipi di evento

| `type` | Entità | Effetto |
|--------|--------|---------|
| `lead/created` | Lead | Crea un nuovo lead. Fallisce se l'`external_id` esiste già. |
| `lead/updated` | Lead | Aggiorna un lead esistente. Fallisce se l'`external_id` non esiste. |
| `deal/created` | Deal | Crea un'opportunità, agganciandola al lead padre. |
| `deal/updated` | Deal | Aggiorna un'opportunità esistente (campi e/o stage). |
| `comment/created` | Commento | Aggiunge una nota a un lead o a un deal. |

### `lead/created` e `lead/updated`

Stesso schema `data` per entrambi:

| Campo | Tipo | Obbligatorio | Note |
|-------|------|:---:|------|
| `external_id` | string | sì | Identificativo del lead nel tuo sistema (chiave di match). |
| `full_name` | string | no | Nome completo. |
| `email` | string | no | Email del lead. |
| `phone_number` | string | no | Numero in formato E.164 (es. `+393334445566`). |
| `custom_params` | object | no | Campi personalizzati liberi (coppie chiave/valore). |

```json
{
  "id": "evt_lead_1",
  "type": "lead/created",
  "data": {
    "external_id": "L-42",
    "full_name": "Mario Rossi",
    "email": "mario@acme.example.com",
    "phone_number": "+393334445566",
    "custom_params": { "origine": "fiera-2026", "interesse": "impianti" }
  }
}
```

### `deal/created` e `deal/updated`

| Campo | Tipo | Obbligatorio | Note |
|-------|------|:---:|------|
| `external_id` | string | sì | Identificativo del deal nel tuo sistema (chiave di match). |
| `title` | string | no | Titolo dell'opportunità. |
| `amount` | number | no | Importo. |
| `currency` | string | no | Valuta (es. `EUR`). |
| `lead_external_id` | string | no | `external_id` del lead **padre** a cui agganciare il deal. |
| `stage_id` | string (UUID) | no | Stage di destinazione per UUID. Vedi [Risoluzione stage e owner](#risoluzione-stage-e-owner). |
| `stage_name` | string | no | Stage per nome (in alternativa a `stage_id`). |
| `owner_id` | string (UUID) | no | Assegnatario per UUID. |
| `owner_name` | string | no | Assegnatario per nome (in alternativa a `owner_id`). |
| `custom_fields` | object | no | Campi personalizzati liberi. |

```json
{
  "id": "evt_deal_1",
  "type": "deal/created",
  "data": {
    "external_id": "D-77",
    "title": "Preventivo impianto",
    "amount": 3500,
    "currency": "EUR",
    "lead_external_id": "L-42",
    "stage_name": "Nuovo",
    "owner_name": "Giulia Bianchi"
  }
}
```

<Callout type="info">
  Il deal si aggancia al lead padre tramite `lead_external_id` (l'`external_id`
  del lead, **non** un id Audin). Se il lead padre non è ancora presente in
  Audin, l'evento viene rifiutato: invia prima l'evento `lead/*` corrispondente
  (vedi [Ordine consigliato](#ordine-e-semantica)).
</Callout>

### `comment/created`

| Campo | Tipo | Obbligatorio | Note |
|-------|------|:---:|------|
| `content` | string | sì | Testo del commento. |
| `external_id` | string | no | Identificativo del commento nel tuo sistema. Se presente, viene usato per la **deduplica**: ripetere lo stesso `external_id` non crea un doppione. |
| `author_name` | string | no | Nome dell'autore originale (conservato come metadato). |
| `lead_external_id` | string | no\* | `external_id` del lead a cui agganciare il commento. |
| `deal_external_id` | string | no\* | `external_id` del deal a cui agganciare il commento. |

<Callout type="warn">
  \* Un commento richiede **esattamente uno** tra `lead_external_id` e
  `deal_external_id`. L'entità padre indicata deve già esistere in Audin.
</Callout>

```json
{
  "id": "evt_comment_1",
  "type": "comment/created",
  "data": {
    "external_id": "C-9",
    "content": "Il cliente preferisce essere richiamato di pomeriggio.",
    "author_name": "Reception",
    "lead_external_id": "L-42"
  }
}
```

## Risoluzione stage e owner

Per i deal puoi indicare stage e assegnatario in **due modi**, mai con un
default silenzioso:

- **Per UUID** (`stage_id` / `owner_id`) — autoritativo. È l'identificativo
  esatto della risorsa Audin.
- **Per nome** (`stage_name` / `owner_name`) — Audin risolve il nome all'interno
  del tuo Account.

Se un nome è **ambiguo** (più corrispondenze) o **non trovato**, l'evento viene
rifiutato con un errore esplicito (`unresolved_reference`): non viene mai
applicato un valore di ripiego.

Gli stage delle pipeline e gli operatori del tuo Account sono visibili nel
dashboard. Gli UUID corrispondenti si recuperano via API REST con
`GET /pipelines` (stage di una pipeline) e `GET /platform-users` (operatori) —
vedi [Lettura & sistema](/docs/api-rest/lettura) e
[Pipeline & deal](/docs/api-rest/pipeline-e-deal).

<Callout type="info">
  Se ometti sia `stage_id` sia `stage_name` per un `deal/created`, il deal entra
  nello stage iniziale della pipeline di vendita di default dell'Account.
</Callout>

## Ordine e semantica

La semantica `created` / `updated` è **stretta** sull'`external_id`:

| Caso | Esito |
|------|-------|
| `*/created` con `external_id` **nuovo** | Crea il record. |
| `*/created` con `external_id` **già esistente** | `409 already_exists`. |
| `*/updated` con `external_id` **esistente** | Aggiorna il record. |
| `*/updated` con `external_id` **inesistente** | `422 not_found`. |
| `deal`/`comment` con padre (`lead_external_id`/`deal_external_id`) **mancante** | `422 parent_not_found`. |

Poiché deal e commenti si agganciano a un'entità padre, invia gli eventi in
**quest'ordine**:

1. `lead/*`
2. `deal/*` (richiede il lead padre)
3. `comment/*` (richiede il lead o il deal padre)

## Idempotency & retry

L'`id` dell'envelope è la chiave di idempotency, **per Account**:

- Se un evento con lo stesso `id` è già stato **processato con successo**, Audin
  **non riesegue** l'azione e risponde con lo stesso esito, marcato
  `"deduplicated": true`.
- Un evento **fallito** può essere **ritentato** con lo stesso `id`: alla
  riuscita verrà processato normalmente.

Questo rende sicuro il retry su timeout o errore di rete: usa sempre lo stesso
`id` per lo stesso evento sorgente.

## Backfill

Per importazioni storiche (backfill) imposta `suppress_triggers: true` (o
l'header `X-Suppress-Triggers: true`): gli eventi vengono scritti **senza**
attivare automazioni né assegnazioni automatiche. Utile per ricaricare uno
storico senza generare notifiche, chiamate o workflow indesiderati.

```bash
curl -X POST https://api.audin.ai/webhooks/ingest/<token> \
  -H "X-API-Key: $API_KEY" \
  -H "X-Suppress-Triggers: true" \
  -H "Content-Type: application/json" \
  -d '{ "id": "backfill_001", "type": "lead/created", "data": { "external_id": "L-1001" } }'
```

## Risposta

In caso di successo l'endpoint risponde `200` con il riepilogo dell'evento
processato:

```json
{
  "status": "processed",
  "entity": "Lead",
  "id": "9b1c2d3e-…",
  "outcome": "created",
  "deduplicated": false
}
```

| Campo | Valori | Significato |
|-------|--------|-------------|
| `status` | `processed` | L'evento è stato accettato. |
| `entity` | `Lead` \| `Deal` \| `Comment` | Entità interessata. |
| `id` | string | `id` Audin del record risultante. |
| `outcome` | `created` \| `updated` \| `none` | Effetto applicato (`none` su una deduplica). |
| `deduplicated` | boolean | `true` se l'evento era già stato processato (nessuna ri-esecuzione). |

## Codici di errore

Gli errori dell'ingestion webhook usano l'envelope con `code`/`message`
(la stessa forma delle rotte autenticate con API Key):

```json
{
  "success": false,
  "error": {
    "code": "unresolved_reference",
    "message": "Stage \"Nuovo\" non trovato"
  }
}
```

| `code` | HTTP | Significato | Azione |
|--------|------|-------------|--------|
| `invalid_payload` | `400` | Envelope o `data` non validi (campo mancante, `type` sconosciuto). | Correggi il payload e ritenta. Lo `message` può indicare il campo. |
| — | `401` | Header `X-API-Key` mancante o invalido. | Verifica la chiave. |
| `webhook_auth_failed` | `403` | Token nell'URL mancante/errato, oppure token e `X-API-Key` non riferiti allo stesso Account. | Verifica di usare l'URL corretto (con il token aggiornato) e l'API Key dello **stesso** Account. Se hai rigenerato il token, aggiorna l'URL. |
| `already_exists` | `409` | `*/created` su un `external_id` già esistente. | Usa `*/updated`, oppure ignora se è un replay. |
| `not_found` | `422` | `*/updated` su un `external_id` inesistente. | Invia prima il `*/created` corrispondente. |
| `parent_not_found` | `422` | Entità padre (lead/deal) non ancora presente in Audin. | Invia prima l'evento del padre (rispetta l'[ordine](#ordine-e-semantica)). |
| `unresolved_reference` | `422` | `stage_name`/`owner_name` ambiguo o non trovato. | Usa l'UUID, o un nome univoco ed esistente. |
| `internal` | `500` | Errore interno. | Ritenta con backoff esponenziale (stesso `id`). |

<Callout type="info">
  Lo **status code** è la fonte di verità per la logica di retry: i `4xx`
  (eccetto `429`) non sono ritentabili senza correggere la richiesta; i `5xx`
  sono ritentabili con backoff. Ogni esito è comunque tracciato nella sezione
  **Sviluppatori** del dashboard.
</Callout>

## Esempio end-to-end

Riversa un lead, la sua opportunità e una nota — nell'ordine corretto:

<Tabs items={["curl", "JavaScript"]}>
<Tab value="curl">

```bash
# 1. Lead
curl -X POST https://api.audin.ai/webhooks/ingest/<token> \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{
    "id": "evt_l_1",
    "type": "lead/created",
    "data": {
      "external_id": "L-42",
      "full_name": "Mario Rossi",
      "email": "mario@acme.example.com",
      "phone_number": "+393334445566"
    }
  }'

# 2. Deal agganciato al lead via lead_external_id
curl -X POST https://api.audin.ai/webhooks/ingest/<token> \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{
    "id": "evt_d_1",
    "type": "deal/created",
    "data": {
      "external_id": "D-77",
      "title": "Preventivo impianto",
      "amount": 3500,
      "currency": "EUR",
      "lead_external_id": "L-42",
      "stage_name": "Nuovo"
    }
  }'

# 3. Commento sul lead
curl -X POST https://api.audin.ai/webhooks/ingest/<token> \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{
    "id": "evt_c_1",
    "type": "comment/created",
    "data": {
      "external_id": "C-9",
      "content": "Richiamare di pomeriggio.",
      "author_name": "Reception",
      "lead_external_id": "L-42"
    }
  }'
```

</Tab>
<Tab value="JavaScript">

```js
// L'URL contiene il token segreto per-Account (aud_ing_…) ottenuto dalla
// pagina "Sviluppatori → Webhook di ingestion": tienilo lato server.
const INGEST_URL = process.env.AUDIN_INGEST_WEBHOOK_URL; // …/webhooks/ingest/aud_ing_…

const ingest = (event) =>
  fetch(INGEST_URL, {
    method: "POST",
    headers: {
      "X-API-Key": process.env.AUDIN_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(event),
  }).then((r) => r.json());

// 1. Lead → 2. Deal → 3. Commento (l'ordine conta: deal e commento
// richiedono che il padre esista già).
await ingest({
  id: "evt_l_1",
  type: "lead/created",
  data: {
    external_id: "L-42",
    full_name: "Mario Rossi",
    email: "mario@acme.example.com",
    phone_number: "+393334445566",
  },
});

await ingest({
  id: "evt_d_1",
  type: "deal/created",
  data: {
    external_id: "D-77",
    title: "Preventivo impianto",
    amount: 3500,
    currency: "EUR",
    lead_external_id: "L-42",
    stage_name: "Nuovo",
  },
});

await ingest({
  id: "evt_c_1",
  type: "comment/created",
  data: {
    external_id: "C-9",
    content: "Richiamare di pomeriggio.",
    author_name: "Reception",
    lead_external_id: "L-42",
  },
});
```

</Tab>
</Tabs>

## Prossimo passo

<Cards>
  <Card href="/docs/api-rest/crm" title="CRM anagrafiche" description="Lead, aziende e contatti via API REST: scrittura e lettura con gli id Audin." />
  <Card href="/docs/api-rest/pipeline-e-deal" title="Pipeline & deal" description="Pipeline, stage e opportunità: recupera gli UUID di stage e operatori." />
</Cards>
