Torna all’App
Torna a Contratti

Webhook in Uscita: Avvisare un Sistema Esterno alla Firma

Quando un cliente firma, qualcosa di solito deve partire anche fuori da SalesControl: creare il suo spazio sulla tua piattaforma, aprire una scheda nel gestionale, avvisare un’automazione. Un webhook in uscita fa esattamente questo: alla firma (o a un passo di un flusso automatico) SalesControl chiama un indirizzo che indichi tu e gli manda i dati del contratto più i campi che scegli.

Piano richiesto

I webhook in uscita sono disponibili dal piano Professional in su. Li gestiscono gli admin e i membri col permesso “Può modificare i modelli di contratto”.

A cosa serve

L’esempio vero da cui è nata la funzione: un’agenzia crea a ogni cliente un sottodominio sul proprio sito. Prima lo faceva a mano, dopo ogni firma. Ora ha un webhook che chiama il proprio server e gli manda un campo subdomain composto da {{customer_name}}.{{customer_last_name}}: il sottodominio nasce da solo.

Attenzione a non confonderlo col suo opposto: i webhook “in entrata” (lead dal gestionale, firma dal gestionale, eventi) sono qualcosa che arriva a SalesControl. Questo è il verso opposto: qualcosa che parte da SalesControl.

Dove si configura

Sul modello di contratto, non nelle impostazioni generali: così ogni modello può avvisare sistemi diversi. In Contratti › Modelli, apri un modello e scorri fino alla sezione Webhook (firma e flussi).

Prima salva il modello

Su un modello appena creato e mai salvato la sezione dice “Salva il modello per aggiungere un webhook”. Salva una volta e il bottone + Webhook si accende.

C’è anche una vista d’insieme: Impostazioni › Integrazioni › Webhook › Webhook in uscita elenca tutti quelli dell’agenzia con nome, modello, evento, indirizzo, stato e ultimo esito, e il bottone Apri il modello per andarli a modificare. Da lì si legge anche il registro delle consegne.

Alla firma o in un passo di flusso

Nella modale, Quando parte sceglie l’evento:

Il messaggio di un passo di flusso è lo stesso della firma (qui sotto), con event = contract.flow_step e in più il blocco flow:

"flow": { "node_id": "n_3fa81c02", "phase": "main" }

Indirizzo, header e sicurezza

Nella modale del webhook compili:

Gli header si scrivono, non si rileggono

I valori degli header restano cifrati e non si rivedono mai più. Riaprendo il webhook il campo mostra “•••• salvato · lascia vuoto per conservare”: lascialo com’è e il valore salvato resta, scrivici sopra per sostituirlo. Se cambi nome a un header, il vecchio valore non si ritrova più e va riscritto.

Due controlli lavorano in silenzio a ogni consegna, non solo quando salvi: l’indirizzo viene ricontrollato al momento dell’invio (una regola nuova vale anche per un webhook salvato mesi fa) e i reindirizzamenti non si seguono. Se il tuo server risponde con un redirect, la consegna risulta fallita: metti nel campo l’indirizzo finale.

Alcuni header li mettiamo noi e non si possono sovrascrivere: content-type, user-agent e tutti quelli che iniziano per x-salescontrol-.

I campi personalizzati e le variabili

Nella sezione Campi aggiungi le coppie Nome del campo e Valore che il tuo sistema si aspetta — fino a 30, con valori fino a 2.000 caratteri. Nel valore usi le variabili dai chip sotto il campo, da sole o mescolate al testo ({{customer_name}}.{{customer_last_name}}, cliente-{{customer_id}}…).

GruppoVariabili
Contattocustomer_name (il primo nome), customer_full_name (il nome completo), customer_last_name, customer_email, customer_phone
Contrattoservice_name, contract_value, duration_months, payment_type, currency, start_date, signature_date
Agenzia e teamagency_name, closer_name, setter_name
Riferimenticustomer_id, contract_id, chat_link, pdf_link

Una variabile riconosciuta ma senza valore (un cliente senza telefono, un contratto senza setter) diventa stringa vuota: il tuo sistema non riceve mai la scritta {{customer_phone}} al posto di un numero. Una variabile scritta male, invece, resta com’è: così il refuso si vede nel registro delle consegne e lo correggi.

Nomi riservati

Un campo non può chiamarsi event, occurred_at, agency, contract, cliente, servizio, team o flow: sono i blocchi standard del messaggio e devono voler dire sempre la stessa cosa.

Com’è fatto il messaggio

La chiamata è un POST con un corpo JSON. Dentro ci sono il blocco standard e, al primo livello accanto a lui, i tuoi campi personalizzati: il tuo sistema legge subdomain e basta, senza doverlo pescare dentro un oggetto annidato.

{
  "event": "contract.signed",
  "occurred_at": "2026-09-18T10:32:07.512Z",
  "agency": {
    "id": "3f2a…",
    "name": "MedWave"
  },
  "contract": {
    "id": "8c41…",
    "template_id": "b07d…",
    "template_name": "Contratto servizi marketing",
    "signed_at": "2026-09-18T10:31:58.004Z",
    "valore_contratto": 4500,
    "durata_mesi": 6,
    "tipo_pagamento": "mensile",
    "currency": "EUR",
    "data_inizio": "2026-10-01",
    "pdf_url": "https://…"
  },
  "cliente": {
    "id": "d914…",
    "contract_id": "d914…",
    "nome": "Mario Rossi",
    "primo_nome": "Mario",
    "cognome": "Rossi",
    "email": "mario.rossi@esempio.it",
    "telefono": "+39 333 1234567",
    "lingua": "it",
    "chat_url": "https://portal.salescontrol.app/c/…"
  },
  "servizio": {
    "id": "5ab2…",
    "nome": "Gestione campagne"
  },
  "team": {
    "closer": { "nome": "Luca", "email": "luca@esempio.it" },
    "setter": { "nome": "Jenny", "email": "jenny@esempio.it" }
  },
  "subdomain": "mario.rossi"
}

L’ultima riga, subdomain, è il campo personalizzato dell’esempio: al posto suo ci sono i tuoi. Un valore che non esiste arriva come null (per esempio telefono per un cliente che non è mai stato un lead, o chat_url se il piano non include la chat). Il pdf_url è un link temporaneo al PDF firmato: vale sette giorni, quindi scarica il file se ti serve conservarlo.

Un cliente, più contratti

Se il cliente firma un secondo contratto in parallelo (un altro servizio mentre il primo è ancora in corso), il messaggio parla sempre della stessa persona: cliente.id (e la variabile customer_id) è l’identificativo che hai ricevuto alla sua prima firma, e nome, email, telefono, lingua e chat_url sono i suoi. cliente.contract_id invece è il contratto appena firmato: servizio, closer, setter e importi del messaggio sono i suoi. Per un cliente con un solo contratto i due identificativi coincidono.

Insieme al corpo arrivano tre header nostri: x-salescontrol-event (qui contract.signed), x-salescontrol-delivery (l’identificativo di quella consegna) e la firma, qui sotto. Per non agire due volte sullo stesso contratto, il riferimento affidabile è contract.id: è sempre lo stesso anche su un secondo tentativo o su un Riprova.

La firma x-salescontrol-signature

Ogni webhook ha il suo Segreto della firma, che trovi nella modale con i bottoni Copia e Rigenera. Serve a chi riceve per verificare che il messaggio arrivi davvero da voi e non sia stato toccato per strada.

L’header ha questa forma:

x-salescontrol-signature: t=1758190327,v1=4f0c1d…

Per controllarla, chi riceve fa tre cose:

1. Prende il corpo così com’è arrivato

Il testo grezzo della richiesta, prima di trasformarlo in oggetto: rileggerlo e riscriverlo cambierebbe spazi e ordine delle chiavi, e la verifica non tornerebbe mai.

2. Compone la stringa da firmare

È il valore di t, un punto, e il corpo: 1758190327.{"event":"contract.signed",…}.

3. Calcola HMAC SHA-256 col segreto e confronta

Il risultato in esadecimale deve essere identico a v1. È la stessa ricetta che usa Stripe per i suoi webhook: qualsiasi libreria che già li verifica va bene.

Conviene anche rifiutare i messaggi troppo vecchi, confrontando t con l’ora attuale. Il timestamp è fresco a ogni tentativo: il terzo, che parte sei secondi dopo, non nasce già scaduto.

Rigenerare il segreto lo cambia per tutti

Premendo Rigenera: “Il segreto attuale smette di valere: chi verifica la firma dovrà usare quello nuovo”. Aggiorna prima il tuo sistema, o le consegne successive verranno rifiutate.

Il registro delle consegne e «Riprova»

Ogni tentativo lascia una riga. Prima di mandare un contratto vero puoi premere Invia una prova nella modale: parte una consegna reale con dati di esempio, e il registro la segna col badge Prova.

Il registro completo — Ultime consegne, 50 righe — sta in Impostazioni › Integrazioni › Webhook › Webhook in uscita, con Data, Webhook, Esito, HTTP, Tentativi, e per ogni riga Dettagli (che mostra Messaggio inviato e Risposta) e Riprova, che rimanda lo stesso identico corpo.

Su come si ritenta da sé:

Il registro conserva il corpo inviato, mai gli header: i loro valori restano cifrati e non si rileggono da nessuna parte.

Quando NON parte

Chi firma non aspetta

La chiamata parte in secondo piano: anche se il tuo server ci mette dieci secondi e serve un secondo tentativo, il cliente vede subito la sua pagina e riceve il PDF.

Un dettaglio utile: customer_phone viene dal lead d’origine, perché la scheda cliente non ha un campo telefono. Un contratto mandato a un cliente che non è mai stato un lead lo manda vuoto.