Coonvert ← Torna al sito

Documentazione API

API REST e webhook di Coonvert · versione v1

Cosa si può fare

Con l'API colleghi Coonvert al tuo CRM. Puoi:

API e webhook sono inclusi nel piano Agency. Ogni token vale per un solo account: se gestisci più clienti, crea un token dentro ogni cliente.

Primo passo: il token

  1. Entra nel pannello e apri l'account (o il cliente) da collegare.
  2. Vai su Integrazioni → API e crea un token. Copialo subito: non sarà più mostrato.
  3. Prova il token:
curl https://www.coonvert.com/api/v1/user \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json"

Risposta

{
    "id": 57,
    "name": "Mario Rossi",
    "email": "mario.rossi@example.com",
    "role": "owner"
}

Il token agisce con i permessi del suo utente. Per leggere e modificare i lead l'utente deve poter vedere i dati dei lead nell'account del token.

Autenticazione e indirizzo

Tutte le chiamate partono da questo indirizzo:

https://www.coonvert.com/api/v1

Manda il token in ogni richiesta, con queste intestazioni:

Authorization: Bearer <token>
Accept: application/json
Content-Type: application/json

Corpi e risposte sono in JSON (UTF-8). Le date sono in formato ISO 8601 e in UTC, ad esempio 2026-10-27T16:40:00+00:00. Un token mancante o revocato risponde 401.

Il lead

L'API restituisce il lead sempre in questa forma, uguale a quella dei webhook.

{
    "id": 4821,
    "name": "Mario Rossi",
    "email": "mario.rossi@example.com",
    "phone": "+393331234567",
    "page": "Consulenza gratuita",
    "page_id": 312,
    "form_id": null,
    "status": "convertito",
    "valore": 1250.5,
    "convertito_at": "2026-10-27T16:40:00+00:00",
    "source": "form",
    "source_detail": null,
    "data": {
        "messaggio": "Vorrei un preventivo per il mio ristorante"
    },
    "utm": {
        "source": "facebook",
        "medium": "cpc",
        "campaign": "autunno-2026",
        "term": null,
        "content": null
    },
    "gruppi": [
        "Caldi"
    ],
    "campi": {
        "budget": 5000,
        "settore": "Ristorazione"
    },
    "richiamo": {
        "quando": "2026-10-29T09:00:00+00:00",
        "nota": "Mandare il preventivo"
    },
    "created_at": "2026-10-25T09:12:30+00:00",
    "updated_at": "2026-10-27T16:40:00+00:00",
    "utm_source": "facebook",
    "utm_campaign": "autunno-2026"
}
CampoTipoSignificato
idnumeroIdentificativo del lead. Usalo come chiave nel CRM.
name, email, phonetesto o nullDati del contatto. C'è sempre almeno l'email o il telefono.
pagetesto o nullTitolo della pagina da cui è arrivato.
page_idnumero o nullId della pagina (vedi GET /pages).
form_idnumero o nullId del form esterno da cui è arrivato.
statustestoStato di lavorazione, vedi sotto.
valorenumero o nullValore in euro della vendita (es. 1250.5). Esiste solo per i lead convertiti.
convertito_atdata o nullQuando è diventato convertito l'ultima volta.
sourcetestoProvenienza: form, meta, api, pagamento, manuale, form_esterno, email, webhook, importazione. I lead creati con l'API hanno sempre api.
source_detailtesto o nullProvenienza leggibile, es. «Google Ads – campagna primavera».
dataoggetto o nullGli altri campi inviati col form (o custom nel POST).
utmoggettosource, medium, campaign, term, content (ognuno testo o null).
gruppilista di testiNomi dei gruppi del lead, in ordine alfabetico. Vuota se non ne ha.
campioggettoCampi personalizzati, per chiave. Solo i campi che esistono oggi nell'account.
created_at, updated_atdataCreazione e ultima modifica. updated_at cambia anche quando cambiano gruppi o richiamo.
utm_source, utm_campaigntesto o nullDeprecati: restano per compatibilità, usa utm. Non ci sono nei webhook.

Stati possibili

ValoreNel pannello
nuovoNuovo
contattatoContattato
qualificatoQualificato
convertitoConvertito (chiude il lead)
persoPerso (chiude il lead)
falso_duplicatoFalso/Duplicato (chiude il lead)

Uno stato che chiude il lead toglie il richiamo in programma. Diventando convertito si aggiorna convertito_at.

Campi personalizzati e gruppi

I campi personalizzati si creano nel pannello in Lead & Analisi → Campi personalizzati. Ogni campo ha una chiave (es. budget) e un tipo. Nei corpi JSON il valore segue il tipo:

TipoValore
Testotesto, massimo 255 caratteri
Testo lungotesto, massimo 5000 caratteri
Numeronumero
Datatesto AAAA-MM-GG, es. 2026-11-15
Sceltauna delle opzioni del campo, scritta uguale
Sì/Notrue o false

Un valore vuoto (null o "") toglie il campo dal lead. Una chiave che non esiste risponde 422 con l'elenco delle chiavi valide. I gruppi si assegnano per nome, nel PATCH e nel POST (vedi PATCH /leads/{id}); l'elenco di quelli esistenti e dei campi è in Gruppi e campi.

Endpoint

I percorsi qui sotto si aggiungono all'indirizzo https://www.coonvert.com/api/v1.

GET/leads

Elenco dei lead, a pagine. Senza filtri parte dal più recente.

ParametroSignificato
updated_sinceSolo i lead con updated_at uguale o successivo a questa data (ISO 8601). Ordina dal meno recente: è il filtro per la sincronizzazione.
created_sinceSolo i lead creati da questa data in poi (ISO 8601).
statusUno degli stati.
page_id, form_idSolo i lead di quella pagina o di quel form.
per_pageLead per pagina, da 1 a 100. Predefinito 50.
pageNumero della pagina, da 1.

Ordine: con updated_since per updated_at crescente, poi id crescente; senza, per data di creazione decrescente, poi id decrescente. Un parametro non valido risponde 422.

curl -G https://www.coonvert.com/api/v1/leads \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "status=convertito" \
  --data-urlencode "per_page=100"

Risposta

{
    "data": [
        {
            "id": 4821,
            "name": "Mario Rossi",
            "email": "mario.rossi@example.com",
            "phone": "+393331234567",
            "page": "Consulenza gratuita",
            "page_id": 312,
            "form_id": null,
            "status": "convertito",
            "valore": 1250.5,
            "convertito_at": "2026-10-27T16:40:00+00:00",
            "source": "form",
            "source_detail": null,
            "data": {
                "messaggio": "Vorrei un preventivo per il mio ristorante"
            },
            "utm": {
                "source": "facebook",
                "medium": "cpc",
                "campaign": "autunno-2026",
                "term": null,
                "content": null
            },
            "gruppi": [
                "Caldi"
            ],
            "campi": {
                "budget": 5000,
                "settore": "Ristorazione"
            },
            "richiamo": {
                "quando": "2026-10-29T09:00:00+00:00",
                "nota": "Mandare il preventivo"
            },
            "created_at": "2026-10-25T09:12:30+00:00",
            "updated_at": "2026-10-27T16:40:00+00:00",
            "utm_source": "facebook",
            "utm_campaign": "autunno-2026"
        }
    ],
    "links": {
        "first": "https://www.coonvert.com/api/v1/leads?per_page=100&status=convertito&page=1",
        "last": "https://www.coonvert.com/api/v1/leads?per_page=100&status=convertito&page=3",
        "prev": null,
        "next": "https://www.coonvert.com/api/v1/leads?per_page=100&status=convertito&page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 3,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "page": null,
                "active": false
            },
            {
                "url": "https://www.coonvert.com/api/v1/leads?per_page=100&status=convertito&page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": "https://www.coonvert.com/api/v1/leads?per_page=100&status=convertito&page=2",
                "label": "Next &raquo;",
                "page": 2,
                "active": false
            }
        ],
        "path": "https://www.coonvert.com/api/v1/leads",
        "per_page": 100,
        "to": 1,
        "total": 201
    }
}

links.next è null sull'ultima pagina. meta.total è il numero totale dei lead che corrispondono ai filtri.

Errori possibili: 401, 403, 422, 429.

Sincronizzazione incrementale

Per tenere il CRM allineato senza rileggere tutto:

  1. Salva l'updated_at dell'ultimo lead che hai letto (la prima volta usa una data lontana, es. 2020-01-01T00:00:00Z).
  2. Chiedi GET /leads?updated_since=<quel valore>&per_page=100.
  3. Per ogni lead: se nel CRM c'è già lo stesso id aggiornalo, altrimenti crealo. Deduplica sempre per id: il filtro include il valore indicato, quindi l'ultimo lead della volta prima torna anche questa volta.
  4. Salva l'updated_at dell'ultimo lead ricevuto e richiedi di nuovo la prima pagina da lì. Ripeti finché ricevi meno di 100 lead.
  5. Solo se tutti i 100 lead hanno lo stesso updated_at (es. dopo una modifica di massa) passa a page=2, page=3… con lo stesso valore, finché l'updated_at cambia.

Ripartire ogni volta dalla prima pagina evita di saltare lead che cambiano mentre stai leggendo. Nella query string il + del fuso va codificato (%2B): con cURL usa --data-urlencode.

curl -G https://www.coonvert.com/api/v1/leads \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "updated_since=2026-10-27T16:40:00+00:00" \
  --data-urlencode "per_page=100"

Esempio in JavaScript (Node 18+)

async function sincronizza(cursore, salvaLead, salvaCursore) {
  let pagina = 1;
  for (;;) {
    const url = new URL('https://www.coonvert.com/api/v1/leads');
    url.search = new URLSearchParams({ updated_since: cursore, per_page: '100', page: String(pagina) });
    const risposta = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.COONVERT_TOKEN}`, Accept: 'application/json' },
    });
    if (!risposta.ok) throw new Error(`Coonvert ha risposto ${risposta.status}`);
    const { data } = await risposta.json();

    for (const lead of data) await salvaLead(lead); // crea o aggiorna per lead.id

    if (data.length === 0) return cursore;
    const ultimo = data[data.length - 1].updated_at;
    if (data.length < 100) { await salvaCursore(ultimo); return ultimo; } // finito
    if (ultimo === cursore) { pagina++; continue; } // tutta la pagina con lo stesso istante
    cursore = ultimo;
    pagina = 1;
    await salvaCursore(cursore);
  }
}

L'API non segnala i lead eliminati. Se ti servono gli aggiornamenti in tempo reale usa i webhook e tieni la sincronizzazione come controllo periodico.

GET/leads/{id}

Un lead solo.

curl https://www.coonvert.com/api/v1/leads/4821 \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json"

Risposta

{
    "data": {
        "id": 4821,
        "name": "Mario Rossi",
        "email": "mario.rossi@example.com",
        "phone": "+393331234567",
        "page": "Consulenza gratuita",
        "page_id": 312,
        "form_id": null,
        "status": "convertito",
        "valore": 1250.5,
        "convertito_at": "2026-10-27T16:40:00+00:00",
        "source": "form",
        "source_detail": null,
        "data": {
            "messaggio": "Vorrei un preventivo per il mio ristorante"
        },
        "utm": {
            "source": "facebook",
            "medium": "cpc",
            "campaign": "autunno-2026",
            "term": null,
            "content": null
        },
        "gruppi": [
            "Caldi"
        ],
        "campi": {
            "budget": 5000,
            "settore": "Ristorazione"
        },
        "richiamo": {
            "quando": "2026-10-29T09:00:00+00:00",
            "nota": "Mandare il preventivo"
        },
        "created_at": "2026-10-25T09:12:30+00:00",
        "updated_at": "2026-10-27T16:40:00+00:00",
        "utm_source": "facebook",
        "utm_campaign": "autunno-2026"
    }
}

Errori possibili: 401, 403, 404 (inesistente o di un altro account), 429.

POST/leads

Crea un lead. Serve almeno email o phone. La provenienza (source) è sempre api.

CampoSignificato
nameNome, massimo 255 caratteri.
emailEmail valida. Obbligatoria se manca il telefono.
phoneTelefono, massimo 50 caratteri. Obbligatorio se manca l'email.
page_idFacoltativo. Una pagina pubblicata dell'account.
form_idFacoltativo. Un form esterno dell'account. Non insieme a page_id.
source_detailFacoltativo. Provenienza leggibile, massimo 255 caratteri.
campiFacoltativo. Campi personalizzati, come nel PATCH.
gruppiFacoltativo. Elenco di nomi di gruppo già esistenti, es. ["Caldi"].
customFacoltativo. Oggetto libero, finisce in data.
utm_source, utm_medium, utm_campaign, utm_term, utm_contentFacoltativi, massimo 255 caratteri.

Ci sono tre modi:

In tutti e tre i casi il lead entra nelle automazioni con sorgente «API» e manda il webhook lead.created.

Senza pagina né form

curl -X POST https://www.coonvert.com/api/v1/leads \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-contatto-98765" \
  -d '{
    "name": "Mario Rossi",
    "email": "mario.rossi@example.com",
    "phone": "+393331234567",
    "source_detail": "Google Ads – campagna primavera",
    "utm_source": "google",
    "utm_campaign": "primavera",
    "campi": { "budget": 5000 }
  }'

Con una pagina

curl -X POST https://www.coonvert.com/api/v1/leads \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "page_id": 312, "name": "Mario Rossi", "email": "mario.rossi@example.com" }'

Con un form esterno

curl -X POST https://www.coonvert.com/api/v1/leads \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "form_id": 18, "name": "Mario Rossi", "phone": "+393331234567" }'

Risposta (201)

{
    "data": {
        "id": 4821,
        "name": "Mario Rossi",
        "email": "mario.rossi@example.com",
        "phone": "+393331234567",
        "page": null,
        "page_id": null,
        "form_id": null,
        "status": "nuovo",
        "valore": null,
        "convertito_at": null,
        "source": "api",
        "source_detail": "Google Ads – campagna primavera",
        "data": null,
        "utm": {
            "source": "google",
            "medium": null,
            "campaign": "primavera",
            "term": null,
            "content": null
        },
        "gruppi": [],
        "campi": {
            "budget": 5000
        },
        "richiamo": {
            "quando": "2026-10-29T09:00:00+00:00",
            "nota": "Mandare il preventivo"
        },
        "created_at": "2026-10-28T09:30:00+00:00",
        "updated_at": "2026-10-28T09:30:00+00:00",
        "utm_source": "google",
        "utm_campaign": "primavera"
    }
}

Errori possibili: 401, 402 (limite dei lead del piano raggiunto), 403, 404 (pagina non pubblicata, form inesistente o di un altro account, oppure pagine dell'account sospese), 409, 422, 429. Per non creare doppioni nei tentativi vedi l'idempotenza.

PATCH/leads/{id}

Aggiorna un lead dal CRM. Tutti i campi sono facoltativi, ma ne serve almeno uno:

Il richiamo risponde 422 con «Indica quando richiamare.» se manca quando, «Il richiamo deve essere nel futuro.» se la data è passata e «Un lead chiuso non può avere un richiamo.» se il lead è (o diventa nella stessa richiesta) convertito, perso o falso/duplicato. Il lead nelle risposte ha il campo richiamo (quando e nota, oppure null).

Un PATCH che cambia solo il richiamo non manda lead.updated (ma aggiorna updated_at, quindi si vede nella sincronizzazione incrementale). In un PATCH che cambia anche altro, il lead.updated mostra ancora il richiamo di prima: quello nuovo si applica subito dopo il salvataggio.

gruppi non si manda insieme a gruppi_aggiungi o gruppi_togli (422: «Usa gruppi oppure gruppi_aggiungi/gruppi_togli, non entrambi.»). I nomi si confrontano senza badare a maiuscole e spazi ai lati; un nome che non esiste risponde 422 e non crea niente: i gruppi nuovi si creano dal pannello. Anche i gruppi si cambiano solo con il permesso di modifica dei lead.

Nome, email e telefono non si modificano via API. L'effetto è lo stesso di una modifica dal pannello: la cronologia del lead registra il cambio di stato «via API», uno stato chiuso toglie il richiamo, le automazioni che si fermano su quello stato fanno uscire il lead e parte il webhook lead.updated. Se non cambia niente non succede niente: puoi ripetere la stessa richiesta senza effetti doppi.

curl -X PATCH https://www.coonvert.com/api/v1/leads/4821 \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "status": "convertito", "valore": 1250.50, "campi": { "settore": "Ristorazione" } }'

Risposta

{
    "data": {
        "id": 4821,
        "name": "Mario Rossi",
        "email": "mario.rossi@example.com",
        "phone": "+393331234567",
        "page": "Consulenza gratuita",
        "page_id": 312,
        "form_id": null,
        "status": "convertito",
        "valore": 1250.5,
        "convertito_at": "2026-10-27T16:40:00+00:00",
        "source": "form",
        "source_detail": null,
        "data": {
            "messaggio": "Vorrei un preventivo per il mio ristorante"
        },
        "utm": {
            "source": "facebook",
            "medium": "cpc",
            "campaign": "autunno-2026",
            "term": null,
            "content": null
        },
        "gruppi": [
            "Caldi"
        ],
        "campi": {
            "budget": 5000,
            "settore": "Ristorazione"
        },
        "richiamo": {
            "quando": "2026-10-29T09:00:00+00:00",
            "nota": "Mandare il preventivo"
        },
        "created_at": "2026-10-25T09:12:30+00:00",
        "updated_at": "2026-10-27T16:40:00+00:00",
        "utm_source": "facebook",
        "utm_campaign": "autunno-2026"
    }
}

Valore su un lead non convertito (422)

{
    "message": "Il valore si imposta solo su un lead convertito.",
    "errors": {
        "valore": [
            "Il valore si imposta solo su un lead convertito."
        ]
    }
}

Errori possibili: 401, 403 (ruolo senza permesso di modifica, o account in sola lettura), 404, 422, 429.

GET/pages

Le pagine dell'account, dalla più recente, a pagine come /leads (per_page da 1 a 100, predefinito 50; page). Serve a trovare il page_id.

curl -G https://www.coonvert.com/api/v1/pages \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "per_page=100"

Risposta

{
    "data": [
        {
            "id": 312,
            "title": "Consulenza gratuita",
            "slug": "consulenza-gratuita",
            "status": "pubblicata",
            "published_at": "2026-10-01T08:00:00+00:00",
            "created_at": "2026-09-28T14:05:10+00:00"
        }
    ],
    "links": {
        "first": "https://www.coonvert.com/api/v1/pages?per_page=100&page=1",
        "last": "https://www.coonvert.com/api/v1/pages?per_page=100&page=3",
        "prev": null,
        "next": "https://www.coonvert.com/api/v1/pages?per_page=100&page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 3,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "page": null,
                "active": false
            },
            {
                "url": "https://www.coonvert.com/api/v1/pages?per_page=100&page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": "https://www.coonvert.com/api/v1/pages?per_page=100&page=2",
                "label": "Next &raquo;",
                "page": 2,
                "active": false
            }
        ],
        "path": "https://www.coonvert.com/api/v1/pages",
        "per_page": 100,
        "to": 1,
        "total": 201
    }
}

status è bozza o pubblicata. Errori possibili: 401, 403, 422, 429.

GET/pages/{id}

curl https://www.coonvert.com/api/v1/pages/312 \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json"

Risposta

{
    "data": {
        "id": 312,
        "title": "Consulenza gratuita",
        "slug": "consulenza-gratuita",
        "status": "pubblicata",
        "published_at": "2026-10-01T08:00:00+00:00",
        "created_at": "2026-09-28T14:05:10+00:00"
    }
}

Errori possibili: 401, 403, 404, 429.

GET/user

L'utente del token: utile per controllare che il token funzioni (vedi Primo passo). Risponde id, name, email, role, senza data attorno.

DELETE/leads/{id}

Elimina il lead per sempre, con il suo storico, le note e i percorsi nelle automazioni. Risponde 204 senza corpo. Serve il permesso di eliminare i lead, che hanno il proprietario e gli editor dell'account; con un altro ruolo la risposta è 403 («Il tuo ruolo non permette di eliminare i lead.»). Un lead già eliminato risponde 404.

curl -X DELETE https://www.coonvert.com/api/v1/leads/4821 \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json"

Parte l'evento lead.deleted (vedi Webhook), anche quando il lead si elimina dal pannello. Con la cancellazione dell'intero account non parte nessun evento. Le chiavi di idempotenza del lead spariscono con lui: dopo una cancellazione, un POST /leads con la stessa Idempotency-Key crea un lead nuovo. Errori possibili: 401, 403, 404, 429.

Storico ed esiti

Lo storico è la cronologia del lead che vedi nel pannello. Come per gli altri endpoint dei lead, serve un ruolo che vede i dati dei lead (403 altrimenti).

GET/leads/{id}/activities

Le voci dello storico, dalla più recente, a pagine come /leads (per_page da 1 a 100, predefinito 50; fuori da questo intervallo 422). Ogni voce ha type (il tipo di evento, ad esempio esito), descrizione, nota (o null), utente (il nome di chi l'ha registrata, null se è stato il sistema) e created_at in UTC.

curl https://www.coonvert.com/api/v1/leads/4821/activities \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json"

Risposta

{
    "data": [
        {
            "id": 9150,
            "type": "esito",
            "descrizione": "Telefonata: ha risposto",
            "nota": "Interessato, vuole il preventivo",
            "utente": "Giulia Bianchi",
            "created_at": "2026-10-27T16:30:00Z"
        }
    ],
    "links": {
        "first": "https://www.coonvert.com/api/v1/leads/4821/activities&page=1",
        "last": "https://www.coonvert.com/api/v1/leads/4821/activities&page=3",
        "prev": null,
        "next": "https://www.coonvert.com/api/v1/leads/4821/activities&page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 3,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "page": null,
                "active": false
            },
            {
                "url": "https://www.coonvert.com/api/v1/leads/4821/activities&page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": "https://www.coonvert.com/api/v1/leads/4821/activities&page=2",
                "label": "Next &raquo;",
                "page": 2,
                "active": false
            }
        ],
        "path": "https://www.coonvert.com/api/v1/leads/4821/activities",
        "per_page": 100,
        "to": 1,
        "total": 201
    }
}

Errori possibili: 401, 403, 404, 422, 429.

POST/leads/{id}/outcomes

Registra l'esito di un contatto, come «Registra esito» nel pannello. Campi:

curl -X POST https://www.coonvert.com/api/v1/leads/4821/outcomes \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "come": "telefonata", "risultato": "risposto", "nota": "Interessato, vuole il preventivo", "richiamo": { "quando": "2026-11-02T09:00:00+01:00", "nota": "Mandare il preventivo" } }'

Risposta (201)

{
    "data": {
        "id": 4821,
        "name": "Mario Rossi",
        "email": "mario.rossi@example.com",
        "phone": "+393331234567",
        "page": "Consulenza gratuita",
        "page_id": 312,
        "form_id": null,
        "status": "convertito",
        "valore": 1250.5,
        "convertito_at": "2026-10-27T16:40:00+00:00",
        "source": "form",
        "source_detail": null,
        "data": {
            "messaggio": "Vorrei un preventivo per il mio ristorante"
        },
        "utm": {
            "source": "facebook",
            "medium": "cpc",
            "campaign": "autunno-2026",
            "term": null,
            "content": null
        },
        "gruppi": [
            "Caldi"
        ],
        "campi": {
            "budget": 5000,
            "settore": "Ristorazione"
        },
        "richiamo": {
            "quando": "2026-10-29T09:00:00+00:00",
            "nota": "Mandare il preventivo"
        },
        "created_at": "2026-10-25T09:12:30+00:00",
        "updated_at": "2026-10-27T16:40:00+00:00",
        "utm_source": "facebook",
        "utm_campaign": "autunno-2026"
    }
}

È il lead aggiornato. Errori possibili: 401, 403 (ruolo senza permesso di modifica, o account in sola lettura), 404, 422, 429.

Ordini

Gli ordini delle pagine di vendita, in tutti gli stati (in attesa, pagato, fallito, rimborsato, annullato). Servono i pagamenti nel piano e un ruolo che vede i dati dei lead; senza i pagamenti la risposta è 403 («Il tuo piano non include i pagamenti.»), senza i dati dei lead 403 («Il tuo ruolo non permette di vedere i dati dei lead.»).

GET/orders

Gli ordini dell'account, dal più recente, a pagine come /leads. Filtri facoltativi:

curl -G https://www.coonvert.com/api/v1/orders \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode "status=pagato"

Risposta

{
    "data": [
        {
            "order": {
                "id": 88,
                "status": "pagato",
                "amount": 29000,
                "currency": "EUR",
                "option_name": "Consulenza completa",
                "provider": "stripe",
                "mode": "live",
                "provider_order_id": "cs_live_a1B2c3D4",
                "customer_email": "mario.rossi@example.com",
                "paid_at": "2026-10-27T16:38:00+00:00",
                "created_at": "2026-10-27T16:35:00+00:00",
                "updated_at": "2026-10-27T16:38:00+00:00"
            },
            "lead": {
                "id": 4821,
                "name": "Mario Rossi",
                "email": "mario.rossi@example.com",
                "phone": "+393331234567",
                "page": "Consulenza gratuita",
                "page_id": 312,
                "form_id": null,
                "status": "convertito",
                "valore": 1250.5,
                "convertito_at": "2026-10-27T16:40:00+00:00",
                "source": "form",
                "source_detail": null,
                "data": {
                    "messaggio": "Vorrei un preventivo per il mio ristorante"
                },
                "utm": {
                    "source": "facebook",
                    "medium": "cpc",
                    "campaign": "autunno-2026",
                    "term": null,
                    "content": null
                },
                "gruppi": [
                    "Caldi"
                ],
                "campi": {
                    "budget": 5000,
                    "settore": "Ristorazione"
                },
                "richiamo": {
                    "quando": "2026-10-29T09:00:00+00:00",
                    "nota": "Mandare il preventivo"
                },
                "created_at": "2026-10-25T09:12:30+00:00",
                "updated_at": "2026-10-27T16:40:00+00:00"
            },
            "page": {
                "id": 312,
                "title": "Consulenza gratuita",
                "slug": "consulenza-gratuita"
            },
            "funnel": {
                "id": 21,
                "name": "Funnel consulenza",
                "slug": "funnel-consulenza"
            }
        }
    ],
    "links": {
        "first": "https://www.coonvert.com/api/v1/orders?status=pagato&page=1",
        "last": "https://www.coonvert.com/api/v1/orders?status=pagato&page=3",
        "prev": null,
        "next": "https://www.coonvert.com/api/v1/orders?status=pagato&page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 3,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "page": null,
                "active": false
            },
            {
                "url": "https://www.coonvert.com/api/v1/orders?status=pagato&page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": "https://www.coonvert.com/api/v1/orders?status=pagato&page=2",
                "label": "Next &raquo;",
                "page": 2,
                "active": false
            }
        ],
        "path": "https://www.coonvert.com/api/v1/orders",
        "per_page": 100,
        "to": 1,
        "total": 201
    }
}

L'importo amount è in centesimi della currency. La forma è quella del corpo dell'evento order.paid, con in più order.updated_at; lead, page e funnel sono null quando l'ordine non li ha. Errori possibili: 401, 403, 422, 429.

GET/orders/{id}

Un ordine, nella stessa forma, dentro data. Errori possibili: 401, 403, 404, 429.

Webhook

Con i webhook Coonvert chiama il tuo indirizzo quando succede qualcosa. Si configurano in Integrazioni → Webhook: indirizzo pubblico (https consigliato), eventi da ricevere e un secret per la firma.

EventoQuandoCorpo
lead.createdArriva un lead nuovo, da qualunque fonte (anche dall'API).Il lead.
lead.updatedCambiano stato, nome, email, telefono, valore o campi personalizzati; cambiano anche le note, che l'evento segnala ma non fanno parte del lead nel corpo. Cambiano anche i gruppi. I richiami no.Il lead più origine.
lead.deletedUn lead viene eliminato: dall'API o dal pannello, uno alla volta o in blocco. Non parte quando si elimina l'intero account.id, email, deleted_at, origine.
order.paidUn ordine risulta pagato (con i pagamenti nel piano).order, lead, page, funnel, come in GET /orders.
order.refundedUn ordine pagato passa a rimborsato; parte una volta sola.Lo stesso corpo di order.paid. L'endpoint di notifica di uno step di pagamento riceve anche order.refunded: distingui i due con l'intestazione X-Coonvert-Event o con order.status.
page.publishedUna pagina viene pubblicata.id, title, slug, status, published_at.

La richiesta è un POST con il JSON nel corpo, senza involucro: il corpo è direttamente il lead (o la pagina). Il lead ha la stessa forma dell'API, senza i campi deprecati utm_source e utm_campaign. Intestazioni:

Content-Type: application/json
X-Coonvert-Event: lead.updated
X-Coonvert-Signature: sha256=<HMAC-SHA256 del corpo con il secret, in esadecimale>

Corpo di lead.updated

{
    "id": 4821,
    "name": "Mario Rossi",
    "email": "mario.rossi@example.com",
    "phone": "+393331234567",
    "page": "Consulenza gratuita",
    "page_id": 312,
    "form_id": null,
    "status": "convertito",
    "valore": 1250.5,
    "convertito_at": "2026-10-27T16:40:00+00:00",
    "source": "form",
    "source_detail": null,
    "data": {
        "messaggio": "Vorrei un preventivo per il mio ristorante"
    },
    "utm": {
        "source": "facebook",
        "medium": "cpc",
        "campaign": "autunno-2026",
        "term": null,
        "content": null
    },
    "gruppi": [
        "Caldi"
    ],
    "campi": {
        "budget": 5000,
        "settore": "Ristorazione"
    },
    "richiamo": {
        "quando": "2026-10-29T09:00:00+00:00",
        "nota": "Mandare il preventivo"
    },
    "created_at": "2026-10-25T09:12:30+00:00",
    "updated_at": "2026-10-27T16:40:00+00:00",
    "origine": "coonvert"
}

Corpo di lead.deleted

{
    "id": 4821,
    "email": "mario.rossi@example.com",
    "deleted_at": "2026-10-30T11:05:00+00:00",
    "origine": "api"
}

Il pulsante «Test» del pannello manda invece {"test": true, "message": "Evento di test da Coonvert"}.

Verificare la firma

Calcola l'HMAC-SHA256 del corpo così come è arrivato (prima di decodificare il JSON) e confrontalo con l'intestazione in tempo costante.

PHP

<?php
$secret = getenv('COONVERT_WEBHOOK_SECRET');
$corpo = file_get_contents('php://input');
$firma = $_SERVER['HTTP_X_COONVERT_SIGNATURE'] ?? '';
$attesa = 'sha256=' . hash_hmac('sha256', $corpo, $secret);

if (! hash_equals($attesa, $firma)) {
    http_response_code(401);
    exit;
}

$evento = $_SERVER['HTTP_X_COONVERT_EVENT'] ?? '';
$lead = json_decode($corpo, true);
// ... salva il lead, poi rispondi 2xx
http_response_code(200);

Node (Express)

const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/coonvert', express.raw({ type: 'application/json' }), (req, res) => {
  const attesa = Buffer.from(
    'sha256=' + crypto.createHmac('sha256', process.env.COONVERT_WEBHOOK_SECRET).update(req.body).digest('hex')
  );
  const firma = Buffer.from(req.get('X-Coonvert-Signature') || '');

  if (firma.length !== attesa.length || !crypto.timingSafeEqual(firma, attesa)) {
    return res.sendStatus(401);
  }

  const evento = req.get('X-Coonvert-Event');
  const lead = JSON.parse(req.body.toString('utf8'));
  // ... salva il lead
  res.sendStatus(200);
});

Tentativi

Una consegna riesce se il tuo indirizzo risponde con un codice 2xx entro 10 secondi. Altrimenti Coonvert riprova: al massimo 3 tentativi in tutto, il secondo dopo circa 10 secondi e il terzo dopo circa 1 minuto. Le consegne fallite restano in Integrazioni → Webhook con il motivo e si possono rispedire. Rispondi subito e fai il lavoro lungo dopo; lo stesso evento può arrivare più di una volta, quindi riconoscilo per id e updated_at.

Il campo origine: niente eco

Se il tuo CRM aggiorna un lead con il PATCH, Coonvert manda anche a te il lead.updated di quella modifica. Per non riapplicarla (e rimandarla, in un giro senza fine) guarda origine:

origine c'è solo in lead.updated. Se più sistemi usano l'API sullo stesso account, confronta anche i valori con quelli che hai già prima di scartare l'evento.

Webhook via API

Gli stessi webhook del pannello si gestiscono anche via API: serve a Zapier, Make e simili, che si iscrivono da soli. Le iscrizioni fatte via API compaiono in Integrazioni → Webhook con origine "api"; quelle fatte dal pannello hanno "pannello" e si leggono nello stesso elenco. Il tetto è di 25 iscrizioni per account: contano tutte, quelle create via API e quelle create dal pannello, e oltre il tetto non se ne aggiungono altre né da una parte né dall'altra.

Questi tre percorsi li usa solo il titolare (owner) dell'account, con un piano che include i webhook.

GET/webhooks

Le iscrizioni dell'account, dalla più vecchia. Il secret non c'è: si vede solo alla creazione.

{
    "data": [
        {
            "id": 17,
            "url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
            "events": [
                "lead.created",
                "lead.updated"
            ],
            "is_active": true,
            "origine": "api",
            "created_at": "2026-10-30T10:15:00+00:00"
        }
    ]
}

POST/webhooks

Si iscrive un indirizzo. Campi (tutti obbligatori):

curl -X POST https://www.coonvert.com/api/v1/webhooks \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/", "events": ["lead.created", "lead.updated"] }'

Risposta (201)

{
    "data": {
        "id": 17,
        "url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
        "events": [
            "lead.created",
            "lead.updated"
        ],
        "is_active": true,
        "origine": "api",
        "created_at": "2026-10-30T10:15:00+00:00",
        "secret": "e3b0c44298fc1c149afbf4c8996fb924"
    }
}

Il secret arriva solo in questa risposta: conservalo, serve a verificare la firma delle consegne. Quando le iscrizioni (anche quelle del pannello) sono già 25 la risposta è 422 («Hai raggiunto il numero massimo di webhook (25).»).

DELETE/webhooks/{id}

Toglie l'iscrizione. Risposta 204, senza corpo; 404 se non esiste o è di un altro account.

Da Zapier o Make

Il modo di lavorare è sempre lo stesso: quando lo Zap (o lo scenario) si accende, chiama POST /webhooks con l'indirizzo che gli dà la piattaforma e salva l'id e il secret della risposta. Quando si spegne, chiama DELETE /webhooks/{id}. Per ogni consegna, controlla la firma con il secret ricevuto alla creazione, come in «Verificare la firma».

Errori possibili: 401, 403 («Il tuo piano non include i webhook.», «Solo il titolare dell'account può gestire i webhook.», «Questo account è in sola lettura.» per POST e DELETE), 404, 422, 429.

Gruppi e campi

Due elenchi in sola lettura, utili per riempire i menu a tendina nei programmi collegati. Gruppi e campi si creano dal pannello. Il webhook lead.updated arriva anche quando cambiano i gruppi (dal pannello o via API), una sola volta per modifica, anche se cambiano insieme stato e gruppi.

GET/groups

I gruppi dell'account, in ordine alfabetico.

{
    "data": [
        {
            "id": 9,
            "nome": "Caldi",
            "colore": "#ef4444"
        }
    ]
}

GET/lead-fields

I campi personalizzati. chiave è quella da usare in campi; tipo è uno tra testo, testo_lungo, numero, data, scelta, si_no. opzioni è l'elenco dei valori ammessi per i campi di tipo scelta e null per tutti gli altri.

{
    "data": [
        {
            "chiave": "settore",
            "etichetta": "Settore",
            "tipo": "scelta",
            "opzioni": [
                "Ristorazione",
                "Hotel",
                "Negozi"
            ]
        }
    ]
}

Errori possibili: 401, 403, 429.

Automazioni

Per far entrare un lead in un'automazione (o toglierlo) da un altro programma. Servono un piano che include le automazioni e il permesso di modificare i lead. Le automazioni si costruiscono dal pannello.

GET/automations

Le automazioni dell'account, in ordine alfabetico. Il filtro facoltativo ?stato= accetta attiva, bozza o in_pausa; un altro valore viene ignorato e l'elenco è completo.

{
    "data": [
        {
            "id": 5,
            "nome": "Prova di 15 giorni",
            "stato": "attiva"
        }
    ]
}

POST/automations/{id}/leads

Fa entrare un lead in un'automazione attiva. Campi: lead_id (obbligatorio) e rientra (facoltativo, true per far rientrare un lead che ci è già passato).

curl -X POST https://www.coonvert.com/api/v1/automations/5/leads \
  -H "Authorization: Bearer $COONVERT_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "lead_id": 4821 }'

Risposta (201)

{
    "data": {
        "id": 733,
        "automazione_id": 5,
        "lead_id": 4821,
        "ingresso": 1,
        "stato": "in_corso",
        "motivo": null,
        "entrato_at": "2026-10-30T10:20:00+00:00",
        "uscito_at": null
    }
}

stato è in_corso, finito oppure uscito; motivo dice perché è finito o uscito. Se il lead non entra la risposta è 422 con un motivo leggibile dal programma:

{
    "message": "Il lead non è entrato: non ha un'email.",
    "motivo": "senza_email"
}
motivoSignificato
senza_emailL'automazione parte solo da lead con email e questo non ce l'ha.
gia_dentroIl lead è già dentro l'automazione.
gia_passatoCi è già passato e l'automazione non ammette un secondo giro: con "rientra": true entra comunque.
altro_tenant, senza_inizioNon può entrare (automazione senza inizio valido).

Con un'automazione non attiva la risposta è 422 («L'automazione non è attiva.», senza motivo).

DELETE/automations/{id}/leads/{lead_id}

Toglie il lead dall'automazione: il percorso in corso diventa uscito con motivo a_mano. Risponde 200 con il percorso; 404 («Il lead non è dentro questa automazione.») se non c'è un percorso in corso.

Errori possibili per i due percorsi: 401, 403 («Il tuo piano non include le automazioni.», «Il tuo ruolo non permette di modificare i lead.», «Questo account è in sola lettura.», «Le pagine di questo account sono sospese.», «Questo account è archiviato.»), 404 (automazione o lead inesistenti), 422, 429.

Limiti, errori, idempotenza

Limite di richieste

Ogni token può fare 120 richieste al minuto. Ogni risposta indica il limite e quante richieste restano:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119

Oltre il limite la risposta è 429 con l'intestazione Retry-After (secondi da aspettare). Aspetta quel tempo e riprova.

Errori

Gli errori sono sempre JSON con un message in italiano. Gli errori di validazione (422) hanno anche errors, con i messaggi per campo:

{
    "message": "Indica una pagina oppure un form, non entrambi.",
    "errors": {
        "page_id": [
            "Indica una pagina oppure un form, non entrambi."
        ]
    }
}
CodiceQuandoMessaggio
401Token mancante, sbagliato o revocato.Token mancante o non valido.
402POST /leads oltre il limite dei lead del piano.Limite lead del piano raggiunto.
403Il piano non include l'API.Questa funzione non è inclusa nel tuo piano.
L'utente del token non ha più accesso all'account.Il token non ha più accesso a questo account.
Il ruolo dell'utente non mostra i dati dei lead.Il tuo ruolo non permette di vedere i dati dei lead.
PATCH con un ruolo che non modifica i lead.Il tuo ruolo non permette di modificare i lead.
PATCH, POST …/outcomes o DELETE su un cliente in attesa di decisione.Questo account è in sola lettura.
DELETE /leads/{id} con un ruolo che non elimina i lead.Il tuo ruolo non permette di eliminare i lead.
/orders senza i pagamenti nel piano.Il tuo piano non include i pagamenti.
404Risorsa inesistente o di un altro account; nel POST anche pagina non pubblicata o pagine dell'account sospese.Risorsa non trovata.
405Metodo non previsto per quell'indirizzo (ad esempio PUT su un lead). L'intestazione Allow elenca i metodi ammessi.Metodo non supportato.
409Due POST insieme con la stessa Idempotency-Key.Richiesta concorrente in corso con la stessa Idempotency-Key, riprova.
422Dati non validi (il messaggio dipende dal campo).es. Lo stato indicato non esiste.
valore su un lead non convertito.Il valore si imposta solo su un lead convertito.
richiamo senza data, nel passato o su un lead chiuso (PATCH).Indica quando richiamare. · Il richiamo deve essere nel futuro. · Un lead chiuso non può avere un richiamo.
page_id e form_id insieme.Indica una pagina oppure un form, non entrambi.
429Troppe richieste.Troppe richieste: riprova tra N secondi.

Idempotenza

Un POST /leads ripetuto (timeout, rete che cade) può creare due lead uguali. Per evitarlo manda l'intestazione Idempotency-Key con un valore unico per ogni lead, ad esempio l'id del contatto nel tuo CRM (massimo 255 caratteri).

Per 24 ore la stessa chiave restituisce il lead già creato invece di crearne un altro. Se la prima richiesta è fallita (es. 402) non resta nulla: puoi riprovare con la stessa chiave. Il PATCH non ne ha bisogno: ripetuto, non cambia niente.

Novità e compatibilità

Da ottobre 2026: