Suivre et intégrer
Connecter n8n ou Make
Fonctionnement
CallShift envoie, votre workflow reçoit. À la fin d'un appel, CallShift envoie à l'adresse de votre workflow un corps JSON qui contient toutes les données de l'appel (résumé, transcription, résultat, informations collectées, enregistrement). Votre workflow n'a pas à interroger CallShift.
Construisez votre workflow autour de ces règles :
| Règle | Valeur | En conséquence, votre workflow |
|---|---|---|
| Événement | Fin d'appel uniquement | Ne reçoit rien quand un appel commence ou qu'un transfert débute |
| Format | POST, Content-Type: application/json | Commence par un déclencheur webhook qui accepte POST |
| Délai de réponse | 10 secondes au plus | Répond 200 immédiatement et fait le traitement ensuite |
| Nouvelles tentatives | Aucune | Rattrape les appels non transmis via l'API, à intervalles réguliers |
| Signature | Aucune | Vérifie un jeton secret dans son adresse |
| Attribution | Un webhook par agent | Peut recevoir les appels de tous vos agents sur une seule adresse |
Le guide du webhook de fin d'appel détaille l'envoi.
Configurer en quatre étapes
Récupérer l'adresse de votre workflow
L'adresse de production d'un nœud Webhook n8n, ou l'adresse d'un webhook personnalisé Make (voir les deux sections suivantes). Elle doit être publique et en HTTPS : CallShift envoie ses requêtes depuis le cloud.
Créer le webhook
Dans le tableau de bord, sur app.callshift.ai/webhooks : un nom (Synchro CRM n8n) et l'adresse. Ou via l'API, avec POST
/v1/webhooks.L'attribuer à l'agent
Sur la page de l'agent, onglet After-Call Automations : activez End of call event, choisissez le webhook dans Select Webhook endpoint, puis cliquez sur Save. Ou via l'API, avec PUT
/v1/agents/{agentId}/webhook. Faites-le pour chaque agent ; un agent dupliqué conserve le webhook de l'agent dont il est la copie.Appeler l'agent
Appelez un numéro sur lequel l'agent répond, puis vérifiez que le workflow a bien reçu l'appel (Tester de bout en bout).
Dans n8n
Commencez le workflow par un nœud Webhook :
| Paramètre | Valeur |
|---|---|
| HTTP Method | POST |
| Path | Gardez le chemin aléatoire proposé par n8n : il est difficile à deviner |
| Respond | Immediately, pour que CallShift reçoive son 200 en moins de 10 secondes, quoi que fasse le workflow ensuite |
- Donnez à CallShift l'adresse de production,
/webhook/..., et activez le workflow. L'adresse de test,/webhook-test/..., n'écoute que pendant que l'éditeur attend un événement de test, et pour un seul appel. - Ajoutez à l'adresse
?token=suivi d'une longue valeur aléatoire, et placez un nœud If juste après le webhook :{{ $json.query.token }}doit être égal à votre jeton, sinon le workflow s'arrête. - L'appel se trouve sous
$json.body:{{ $json.body.callId }},{{ $json.body.summary }}. - Pour construire le workflow avant le premier vrai appel, collez l'exemple de payload ci-dessous comme données épinglées du nœud Webhook (Pin data).
Dans Make
Commencez le scénario par l'application Webhooks, déclencheur Custom webhook, et créez un webhook : Make vous donne aussitôt une adresse HTTPS publique.
- L'adresse est longue et aléatoire : gardez-la secrète, elle joue le rôle du jeton.
- Pour que Make apprenne la structure d'un appel, cliquez sur Redetermine data structure, puis envoyez-lui l'exemple de payload ci-dessous, enregistré sous
callshift-payload.json:
curl -X POST "https://hook.eu1.make.com/<your-webhook-address>" \
-H "Content-Type: application/json" \
-d @callshift-payload.json- Activez le scénario, avec la planification Immediately. Make répond
200dès réception ; si vous ajoutez un module Webhook response, placez-le avant tout module lent. - Les champs se mappent sous la forme
{{1.callId}},{{1.contact.metadata.plan}}, où1est le numéro du module webhook.
Rendre un n8n auto-hébergé accessible
Une adresse Make ou une adresse n8n Cloud fonctionnent telles quelles. Un n8n auto-hébergé répond souvent sur une adresse interne que CallShift, depuis le cloud, ne peut pas atteindre :
| Dans l'adresse | Le problème | Que faire |
|---|---|---|
n8n.company.local | Un nom que seul votre réseau connaît | Publiez un nom DNS public, par exemple un sous-domaine dédié aux webhooks |
:5678 | Le port propre à n8n, inaccessible depuis l'extérieur | Placez n8n derrière un reverse proxy sur le port 443 |
http:// | Non chiffré, alors que le payload transporte des transcriptions et des numéros de téléphone | HTTPS avec un certificat valide |
/webhook-test/ | N'écoute que pendant un test dans l'éditeur | L'adresse de production, /webhook/, avec le workflow actif |
Trois façons courantes de l'exposer, selon votre politique réseau : un reverse proxy que vous exploitez déjà (nginx, Traefik, HAProxy, Caddy), un tunnel sortant qui n'ouvre aucun port entrant (comme Cloudflare Tunnel), ou n8n Cloud. N'exposez que le chemin /webhook/, pas l'éditeur, et renseignez l'adresse publique dans la variable WEBHOOK_URL de n8n. Si votre politique exige une liste d'adresses IP sources, écrivez à support@callshift.ai.
Vérifiez l'adresse telle que CallShift la voit : depuis un ordinateur hors de votre réseau et sans VPN, par exemple via le partage de connexion d'un téléphone. Le nom doit se résoudre en une adresse IP publique :
nslookup n8n.example.com 8.8.8.8Et l'adresse doit répondre à un POST en moins de 10 secondes :
curl -i -X POST "https://n8n.example.com/webhook/<path>?token=<your-token>" \
-H "Content-Type: application/json" \
-d '{"test": true}' \
-w "\nTotal time: %{time_total} s\n"| Résultat | Signification | Que faire |
|---|---|---|
200, une exécution dans n8n, en moins de 10 s | Prêt | Testez de bout en bout |
Could not resolve host | Le nom n'est pas public | Publiez le nom DNS |
Timeout, Connection refused | Port fermé ou pare-feu | Ouvrez le port 443 et routez-le vers le reverse proxy |
SSL certificate problem | Certificat invalide ou auto-signé | Un certificat reconnu, par exemple Let's Encrypt |
404, webhook non enregistré | Workflow inactif, ou adresse de test | Activez le workflow, utilisez /webhook/ |
| Plus de 10 s | Le workflow ne répond qu'à la fin de son exécution | Réglez le nœud Webhook pour qu'il réponde Immediately |
Les champs d'un appel
Un exemple de ce que reçoit votre workflow. Les valeurs sont fictives : vérifiez la structure sur votre premier vrai appel.
{
"callId": "8f14e45f-ceea-467a-9575-6d3c0a5f2b1e",
"type": "outbound",
"status": "COMPLETED",
"createdAt": "2026-10-30T09:11:58.000Z",
"startedAt": "2026-10-30T09:12:00.000Z",
"endedAt": "2026-10-30T09:13:24.000Z",
"duration": 84,
"from": "+33187654321",
"to": "+33612345678",
"callerNumber": "+33612345678",
"campaignId": "3b241101-e2bb-4255-8caf-4136c566a962",
"endedReason": "customer-ended-call",
"isReached": true,
"isSignificant": true,
"isSuccessful": true,
"isSilent": false,
"summary": "Ada booked a visit for Saturday at 10am.",
"transcript": "Assistant: Hello, this is Alice...\nUser: Hi, I'd like to book a visit...",
"recordingUrl": "https://recordings.example.com/recordings/8f14e45f.ogg",
"agent": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Alice" },
"phone": { "id": "0d3a7e6b-2b8e-4c5b-9c4e-5f4b9d2a1c8e", "number": "+33187654321" },
"entity": { "id": "9b2d3c4e-5f60-4a1b-8c2d-3e4f5a6b7c8d", "externalId": "club-42", "name": "Lyon Part-Dieu" },
"qualificationCriteria": { "Visit booked": "true", "Preferred day": "Weekend" },
"contact": {
"id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
"firstName": "Ada",
"lastName": "Lovelace",
"email": "ada@example.com",
"phoneNumber": "+33612345678",
"information": "VIP customer",
"metadata": { "plan": "pro" }
}
}| Champ | Type | Quand | Contenu |
|---|---|---|---|
callId | UUID | Toujours | L'identifiant de l'appel : la clé pour dédoublonner et pour rattraper les appels non transmis |
type | texte | Toujours | inbound, outbound ou webcall |
status | texte | Toujours | COMPLETED pour un appel terminé normalement |
createdAt, startedAt, endedAt | Date ISO 8601, en UTC | createdAt toujours ; les deux autres null si elles sont inconnues | Dates de création, de début et de fin de l'appel |
duration | entier | Quand les dates de l'appel sont connues | En secondes, la même valeur que le billingSeconds de l'appel |
from, to | texte | En général | Appel sortant : votre ligne, puis le client. Appel entrant : le client, puis votre ligne |
callerNumber | texte | Toujours | Le numéro du client, quel que soit le sens de l'appel ; "web-call" pour un appel depuis un navigateur |
campaignId | UUID ou null | Appels de campagne | La campagne qui a passé l'appel |
endedReason | texte | Quand l'appel en a un | Pourquoi l'appel s'est terminé : voir Motifs de fin d'appel |
isReached, isSignificant, isSilent, isSuccessful | booléen | Une fois l'appel analysé | Le résultat : voir Analyse des appels |
summary | texte | Une fois l'appel analysé | Le résumé, vide quand personne n'a parlé |
transcript | texte ou null | Quand quelque chose a été dit | Une ligne par tour de parole, qui commence par User: ou Assistant: |
recordingUrl | URL ou null | Agents sur le moteur vocal actuel | Un lien direct vers l'enregistrement ; peut être vide |
agent | {id, name} | Toujours | L'agent de l'appel |
phone | {id, number} ou null | Toujours | Votre ligne CallShift |
entity | {id, externalId, name} ou null | Toujours | L'entité de l'appel, null si l'agent et le numéro sont partagés : voir Entités |
qualificationCriteria | objet | Une fois l'appel analysé | Une clé par critère de l'agent, avec sa valeur sous forme de texte |
contact | objet | Quand le numéro correspond à un contact | id, phoneNumber, firstName, lastName, email, information, metadata |
Un champ sans valeur est absent du corps : mappez-le comme facultatif.
Quatre pièges de mapping
- Les clés des critères sont leurs noms exacts, accents et majuscules compris, et chaque agent a les siens. Relevez-les dans les réglages de l'agent, et considérez un nom comme figé dès qu'un workflow l'utilise : le renommer vide le mapping, sans qu'aucune erreur soit signalée.
- Les valeurs des critères sont du texte. Un oui ou un non arrive sous la forme
"true"ou"false": comparez-le à du texte, jamais à un booléen. Dans n8n :{{ $json.body.qualificationCriteria["Visit booked"] === "true" }}; dans Make, un filtre avec l'opérateur texte Equal totrue. - Les métadonnées sont sous le contact :
contact.metadata.plan, pasmetadata.plan. Un mauvais chemin donne une valeur vide, sans message d'erreur. - Les dates sont en UTC. Convertissez-les dans votre fuseau horaire avant de les inscrire dans un outil que des personnes consultent. Dans n8n :
{{ DateTime.fromISO($json.body.endedAt).setZone("Europe/Paris").toFormat("yyyy-MM-dd HH:mm") }}; dans Make,formatDateavec le fuseau horaire.
Tester de bout en bout
Pas besoin de simulateur : un vrai appel vers un numéro sur lequel l'agent répond déclenche le webhook, quelques secondes après que vous avez raccroché (le temps de rédiger le résumé et d'extraire les informations). Avant d'appeler, vérifiez que l'adresse répond (vérifications plus haut), que le workflow est actif sur son adresse de production et que le webhook est attribué à l'agent.
| Appel à passer | Ce que le workflow doit recevoir |
|---|---|
| Une conversation complète, jusqu'à l'objectif de l'agent | isSuccessful true, un summary clair, tous les critères attendus |
| Un appel que l'agent doit transférer, s'il fait des transferts | endedReason assistant-forwarded-call |
| Raccrocher au bout de deux secondes | isSignificant false, une transcription presque vide : le workflow doit le traiter sans échouer |
Vérifiez ensuite que :
callIdest présent, et différent à chaque appel ;callerNumberest le numéro depuis lequel vous avez appelé ;transcriptetsummarycorrespondent à la conversation ;- chaque critère que lit votre workflow est présent, sous le nom que vous avez mappé ;
recordingUrlouvre l'enregistrement ;- les dates s'affichent dans vos outils à l'heure de votre fuseau horaire.
| Symptôme | Cause probable | Que faire |
|---|---|---|
| Aucune exécution dans n8n ou Make | Adresse injoignable, ou workflow inactif | Refaites les vérifications d'adresse plus haut |
| Toujours rien | Webhook non attribué à cet agent, ou attribution non enregistrée | Vérifiez l'onglet After-Call Automations de l'agent, puis écrivez à support@callshift.ai en indiquant l'heure de l'appel |
| Une exécution qui échoue | Un mapping sur un mauvais chemin | Comparez-le au corps brut reçu par l'exécution |
Chaque envoi, abouti ou en échec, apparaît aussi dans les logs du tableau de bord, avec la réponse de votre workflow.
Rattraper les appels non transmis
Un envoi qui échoue n'est pas renvoyé. Un second workflow, planifié (toutes les 30 minutes, toutes les heures), récupère les appels via l'API et ajoute ceux qui manquent à votre destination :
- un nœud Schedule Trigger dans n8n, ou un scénario planifié dans Make ;
- une requête HTTP vers GET
/v1/calls, filtrée par agent et par période (frometto, en dates ISO complètes, sur la date de création), avec votre clé API dans un en-têteAuthorization: Bearerenregistré dans les identifiants de l'outil, en parcourant toutes les pages jusqu'àpageCount(voir Pagination) ; - une vérification de chaque
callIdpar rapport à ce que contient déjà votre destination, pour n'ajouter que les appels manquants ; - pour la transcription complète d'un appel, GET
/v1/calls/{callId}.
curl "https://api.callshift.ai/v1/calls?agentId=$AGENT_ID&from=2026-10-30T08:00:00Z&to=2026-10-30T09:00:00Z&page=1&limit=100" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY"Créez une clé réservée à ce workflow sur app.callshift.ai/api-keys, et conservez-la dans les identifiants de l'outil, jamais en clair dans un workflow que vous partagez.
Ajouter des contacts à une campagne
Dans l'autre sens : votre workflow ajoute des contacts à une campagne, qui les appelle. Envoyez-les ensemble, en une seule requête à POST/v1/campaigns/{campaignId}/contacts, plutôt qu'en envoyant une requête par contact.
- Normalisez les numéros au format E.164 (
+33612345678) avant de les envoyer : CallShift identifie les contacts par leur numéro et ne les normalise pas à votre place. - Omettez une adresse e-mail dont vous n'êtes pas sûr. Une adresse e-mail présente mais vide ou mal formée fait rejeter toute la requête avec un
400; gardez les valeurs douteuses dansmetadata, qui accepte n'importe quel JSON. - Une campagne active appelle immédiatement. Faites d'abord un test avec un ou deux numéros internes.
Dans n8n, un nœud Code peut préparer chaque contact, ici pour des numéros français :
// Mode "Run Once for Each Item": one contact per item, French numbers to E.164.
let phone = String($json.phone ?? "").replace(/[^\d+]/g, "");
if (phone.startsWith("00")) phone = "+" + phone.slice(2);
else if (phone.startsWith("0")) phone = "+33" + phone.slice(1);
const email = String($json.email ?? "").trim();
const validEmail = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email);
return {
json: {
phoneNumber: phone,
firstName: $json.firstName,
lastName: $json.lastName,
...(validEmail ? { email } : {}),
metadata: { source: "n8n", ...(email && !validEmail ? { rawEmail: email } : {}) },
},
};Un nœud Aggregate les rassemble ensuite dans une seule liste contacts, et un nœud HTTP Request l'envoie :
curl -X POST "https://api.callshift.ai/v1/campaigns/$CAMPAIGN_ID/contacts" \
-H "Authorization: Bearer $CALLSHIFT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "phoneNumber": "+33612345678", "firstName": "Ada", "metadata": { "source": "n8n" } },
{ "phoneNumber": "+33687654321", "firstName": "Louis", "email": "louis@example.com", "metadata": { "source": "n8n" } }
]
}'Faire construire le workflow par un assistant IA
Trois prompts à coller dans Claude, ChatGPT ou Gemini. Chacun contient ce que l'assistant doit savoir de CallShift : remplacez ce qui est entre crochets.
Construire le workflow de réception
Tu es expert en [n8n / Make]. Aide-moi à construire un workflow qui reçoit les webhooks de fin d'appel de la plateforme vocale CallShift et les envoie vers [CIBLE : notre CRM, un Google Sheet, notre outil de tickets].
Les contraintes de CallShift :
- Un POST JSON par appel terminé, envoyé une seule fois, jamais renvoyé, avec un délai d'attente de 10 secondes.
- Pas de signature : l'adresse contient un paramètre ?token= que le workflow doit vérifier en premier, en rejetant la requête s'il est absent ou faux.
- Répondre 200 immédiatement et faire le traitement ensuite.
- Dédoublonner sur callId.
Le corps : à la racine callId, type, status, createdAt, startedAt, endedAt (ISO 8601 en UTC), duration (en secondes), from, to, callerNumber, campaignId, endedReason, isReached, isSignificant, isSilent, isSuccessful, summary, transcript, recordingUrl, agent {id, name}, phone {id, number}, entity {id, externalId, name} ou null. qualificationCriteria est un objet plat dont les clés sont les noms exacts des critères de l'agent et dont les valeurs sont du texte ("true" ou "false" pour un oui ou un non). contact, absent quand le numéro ne correspond à aucun contact, contient id, phoneNumber, firstName, lastName, email, information et metadata. Un champ sans valeur est omis. transcript est un seul bloc de texte, une ligne par tour de parole, chaque ligne commençant par "User:" ou "Assistant:".
Voici un corps reçu par mon workflow : [COLLER LE JSON BRUT]
Donne-moi les nœuds ou les modules dans l'ordre, les réglages de chacun, l'expression exacte de chaque champ mappé, la conversion des dates vers [FUSEAU HORAIRE], et la façon de traiter les appels très courts (isSignificant false).Rendre un n8n auto-hébergé accessible
Tu es ingénieur réseau. Notre instance n8n répond sur une adresse interne, [ADRESSE INTERNE, par exemple http://n8n.company.local:5678]. Une plateforme SaaS dans le cloud (CallShift) doit lui envoyer des requêtes POST en HTTPS depuis Internet, sur le chemin /webhook/<id>. Elle envoie chaque requête une seule fois et attend une réponse sous 10 secondes.
Notre contexte : [PARE-FEU, DMZ, REVERSE PROXY EXISTANT, HÉBERGEMENT DE N8N (Docker, VM), CONTRAINTES DE NOTRE DSI].
Propose la façon la plus simple et sûre d'exposer UNIQUEMENT le chemin /webhook/ de n8n (pas l'éditeur), en HTTPS sur le port 443 avec un certificat valide. Compare un reverse proxy en DMZ, un tunnel sortant et n8n Cloud. Donne la configuration exacte de l'option que tu recommandes, les variables d'environnement n8n à définir (WEBHOOK_URL en particulier), et les commandes nslookup et curl pour vérifier le tout depuis l'extérieur de notre réseau.Rattraper les appels non transmis
Tu es expert en [n8n / Make]. Le webhook de fin d'appel de CallShift n'est jamais renvoyé : quand notre endpoint est indisponible, l'appel est perdu pour le webhook. Construis un workflow planifié, [TOUTES LES 30 MINUTES / TOUTES LES HEURES], qui :
1. Appelle GET https://api.callshift.ai/v1/calls avec l'en-tête Authorization: Bearer <clé API enregistrée dans les identifiants de l'outil>, filtré par agentId et par période (from et to, en dates ISO 8601 complètes, sur la date de création).
2. Lit toutes les pages : la requête accepte les paramètres page (à partir de 1) et limit (jusqu'à 100), et la réponse renvoie data et pageCount.
3. Compare le callId de chaque appel avec ceux déjà présents dans [CIBLE] et n'ajoute que ceux qui manquent.
4. Pour un appel donné, GET https://api.callshift.ai/v1/calls/{callId} renvoie le détail complet, transcription comprise.
Donne-moi les nœuds ou les modules, leurs réglages et les expressions exactes.