Recevez votre lien de démonstration gratuitement en cliquant ici

Recevez votre lien de démonstration gratuitement en cliquant ici

Logo

Comment utiliser, paramétrer, tester un webhook?

Automatisez vos flux de données en temps réel grâce aux webhooks Magnétis. Connectez votre CRM et recevez des notifications à chaque appel reçu.

Automatisez vos flux de données en temps réel grâce aux webhooks Magnétis. Connectez votre CRM et recevez des notifications à chaque appel reçu.

Pourquoi utiliser les webhooks Magnétis ?

Les webhooks permettent de lancer une action de façon automatique (vers votre CRM par exemple) lorsqu'un appel est reçu sur vos numéros trackés pour connecter vos outils au call-tracking. Contrairement à l'API qui vous permet d'aller chercher les informations et d'interagir avec l'interface Magnétis, le webhook est géré par nos soins et vous permet d'être notifié à chaque fois qu'un évènement se produit sur vos numéros trackés.

Si vous souhaitez réaliser une ou plusieurs actions suite à la réception d'un appel sur vos numéros trackés, vous pouvez également explorer les nombreuses possibilités offertes par notre intégration Zapier.

Paramétrage du webhook

Vous pouvez créer des flux webhooks pour l'ensemble de votre entreprise, agence ou réseaux de points de vente dans la partie Organisation de l'interface Magnétis, pour des réseaux ou comptes spécifiques.

Pour cela, une fois connecté au compte ou réseau souhaité, rendez-vous dans API & connecteurs > Webhooks.

Interface Webhooks Magnétis

Pour ajouter un nouveau webhook, cliquez sur "Ajouter un endpoint", puis complétez le formulaire suivant :

Ajout de endpoint webhook
  • Titre : un titre explicite afin d'identifier le webhook

  • Endpoint : Adresse URL de récolte de l'évènement envoyé par le webhook. C'est ce endpoint qui contiendra le code permettant la récupération et l'intégration des données dans vos propres outils

  • Evènements : Evènement générateur de l'envoi des données vers votre endpoint URL. Les évènements disponibles sont :

    • Call > À la fin d'un appel [call:completed]

    • Call > Lorsqu'un appel est manqué [call:missed]

    • Call > Lorsqu'un appel sonne [call:ringing]

    • Email > Lorsqu'un email est reçu [email:received]

    • Lead > Lorsqu'un lead est créé [lead:created]

  • Customiser cet évènement [optionnel] : vous pouvez sélectionner un groupe de comptes ou de numéros pour lesquels exécuter ce webhook et définir des conditions de durée d'appel en secondes pour son déclenchement :

Configuration avancée webhook
  • Remontée des évènements : statut de votre webhook

Format des données envoyées

Pour exemple, voici un exemple de format de données envoyé pour un webhook de type call:completed

"data": {
"id": "call-0c5ac87f-6d8b-4e94-9ce2-f4773bced2f5",
"account": { "id": "acc-efd1bbeb-afbb-4895-a6d5-40a13558af49", "name": "My account" },
"calling_number": "0033612345678",
"duration": 43,
"end_status": "completed",
"missed": false,
"tracked_number": "0033412345678"
},
"event": "test"

Documentation express : Le modèle de données détaillé est disponible dans la documentation suivante : https://docs.magnetis.io/#calls-GETcalls--id-

Tester le webhook

Dans le détail d'un webhook, la zone de droite vous permet de tester le webhook et d'envoyer à votre endpoint des données d'exemple. Vous obtiendrez le retour de votre endpoint en statut et en data.

Vous pouvez tester les modèles de données pour :

  • la ressource CALL : correspond à un appel entrant

  • la ressource EMAIL : pour les emails trackés reçus

  • la ressource LEAD : lors de la création d'un lead suite à la réception d'un appel entrant ou d'un email tracké

Les 10 dernières tentatives sont affichées en historique :

Historique des tests webhook

Signature des requêtes et sécurisation des échanges

Ce document explique comment réceptionner les webhooks envoyés par notre plateforme et valider leur authenticité grâce à la signature HMAC jointe à chaque requête.

Principe de la signature

Chaque requête webhook contient un header HTTP :

X-CALLTRACKING-SIGNATURE: <valeur base64>
X-CALLTRACKING-SIGNATURE: <valeur base64>
X-CALLTRACKING-SIGNATURE: <valeur base64>

Cette signature est un HMAC-SHA1 calculé à partir du corps de la requête et de votre clé secrète (disponible dans les paramètres du webhook).

Algorithme de calcul

1. Partir d'une chaîne initiale = URL de votre endpoint
2. Trier les clés du payload JSON par ordre alphabétique (récursivement)
3. Pour chaque clé (dans l'ordre trié) :
   - Si la valeur est un objet/tableau récurser
   - Si la valeur est null, false, 0 ou "" ignorer
   - Sinon concaténer : clé + valeur
      (les booléens true deviennent "1")
4. Calculer HMAC-SHA1(chaîne, clé_secrète) avec sortie brute (raw binary)
5. Encoder le résultat en Base64
1. Partir d'une chaîne initiale = URL de votre endpoint
2. Trier les clés du payload JSON par ordre alphabétique (récursivement)
3. Pour chaque clé (dans l'ordre trié) :
   - Si la valeur est un objet/tableau récurser
   - Si la valeur est null, false, 0 ou "" ignorer
   - Sinon concaténer : clé + valeur
      (les booléens true deviennent "1")
4. Calculer HMAC-SHA1(chaîne, clé_secrète) avec sortie brute (raw binary)
5. Encoder le résultat en Base64
1. Partir d'une chaîne initiale = URL de votre endpoint
2. Trier les clés du payload JSON par ordre alphabétique (récursivement)
3. Pour chaque clé (dans l'ordre trié) :
   - Si la valeur est un objet/tableau récurser
   - Si la valeur est null, false, 0 ou "" ignorer
   - Sinon concaténer : clé + valeur
      (les booléens true deviennent "1")
4. Calculer HMAC-SHA1(chaîne, clé_secrète) avec sortie brute (raw binary)
5. Encoder le résultat en Base64

⚠️ Attention aux booleans : true est transmis comme 1 dans la chaîne de signature, et false est ignoré (comme null). Veillez à reproduire ce comportement dans votre implémentation.

Exemple de vérification

Pour ce payload envoyé à https://your-server.com/receive-webhook avec la clé your-signature-key :

{
  "event": "call:completed",
  "data": {
    "id": "cal-abc123",
    "calling_number": "+33612345678",
    "duration": 120,
    "missed": false
  }
}
{
  "event": "call:completed",
  "data": {
    "id": "cal-abc123",
    "calling_number": "+33612345678",
    "duration": 120,
    "missed": false
  }
}
{
  "event": "call:completed",
  "data": {
    "id": "cal-abc123",
    "calling_number": "+33612345678",
    "duration": 120,
    "missed": false
  }
}

La signature attendue est : Q2u7t28KQ8MvtvbD+I3vwHRRLAY=

missed: false est ignoré dans le calcul (valeur falsy). duration: 120 est inclus en tant que chaîne "120".

Implémentations par langage

PHP

<?php

function verifyWebhookSignature(
    string $endpoint,
    string $secretKey,
    array  $payload,
    string $receivedSignature
): bool {
    $computed = computeWebhookSignature($endpoint, $secretKey, $payload);
    return hash_equals($computed, $receivedSignature);
}

function computeWebhookSignature(string $endpoint, string $secretKey, array $payload): string
{
    $str = $endpoint;
    $str = dataLoop($str, $payload);
    return base64_encode(hash_hmac('sha1', $str, $secretKey, true));
}

function dataLoop(string $signature, array $data): string
{
    ksort($data);
    foreach ($data as $key => $value) {
        if (is_array($value)) {
            $signature = dataLoop($signature, $value);
        } elseif ($value != null) {   // loose comparison: skips null, false, 0, ''
            $signature .= $key . $value;
        }
    }
    return $signature;
}

// --- Utilisation dans un contrôleur ---

$endpoint = 'https://your-server.com/receive-webhook'; // votre URL d'endpoint
$secretKey = 'your-signature-key';                      // votre clé secrète
$payload = json_decode(file_get_contents('php://input'), true);
$receivedSignature = $_SERVER['HTTP_X_CALLTRACKING_SIGNATURE'] ?? '';

if (!verifyWebhookSignature($endpoint, $secretKey, $payload, $receivedSignature)) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Traitement du webhook…
http_response_code(200);
echo json_encode(['status' => 'ok']);
<?php

function verifyWebhookSignature(
    string $endpoint,
    string $secretKey,
    array  $payload,
    string $receivedSignature
): bool {
    $computed = computeWebhookSignature($endpoint, $secretKey, $payload);
    return hash_equals($computed, $receivedSignature);
}

function computeWebhookSignature(string $endpoint, string $secretKey, array $payload): string
{
    $str = $endpoint;
    $str = dataLoop($str, $payload);
    return base64_encode(hash_hmac('sha1', $str, $secretKey, true));
}

function dataLoop(string $signature, array $data): string
{
    ksort($data);
    foreach ($data as $key => $value) {
        if (is_array($value)) {
            $signature = dataLoop($signature, $value);
        } elseif ($value != null) {   // loose comparison: skips null, false, 0, ''
            $signature .= $key . $value;
        }
    }
    return $signature;
}

// --- Utilisation dans un contrôleur ---

$endpoint = 'https://your-server.com/receive-webhook'; // votre URL d'endpoint
$secretKey = 'your-signature-key';                      // votre clé secrète
$payload = json_decode(file_get_contents('php://input'), true);
$receivedSignature = $_SERVER['HTTP_X_CALLTRACKING_SIGNATURE'] ?? '';

if (!verifyWebhookSignature($endpoint, $secretKey, $payload, $receivedSignature)) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Traitement du webhook…
http_response_code(200);
echo json_encode(['status' => 'ok']);
<?php

function verifyWebhookSignature(
    string $endpoint,
    string $secretKey,
    array  $payload,
    string $receivedSignature
): bool {
    $computed = computeWebhookSignature($endpoint, $secretKey, $payload);
    return hash_equals($computed, $receivedSignature);
}

function computeWebhookSignature(string $endpoint, string $secretKey, array $payload): string
{
    $str = $endpoint;
    $str = dataLoop($str, $payload);
    return base64_encode(hash_hmac('sha1', $str, $secretKey, true));
}

function dataLoop(string $signature, array $data): string
{
    ksort($data);
    foreach ($data as $key => $value) {
        if (is_array($value)) {
            $signature = dataLoop($signature, $value);
        } elseif ($value != null) {   // loose comparison: skips null, false, 0, ''
            $signature .= $key . $value;
        }
    }
    return $signature;
}

// --- Utilisation dans un contrôleur ---

$endpoint = 'https://your-server.com/receive-webhook'; // votre URL d'endpoint
$secretKey = 'your-signature-key';                      // votre clé secrète
$payload = json_decode(file_get_contents('php://input'), true);
$receivedSignature = $_SERVER['HTTP_X_CALLTRACKING_SIGNATURE'] ?? '';

if (!verifyWebhookSignature($endpoint, $secretKey, $payload, $receivedSignature)) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// Traitement du webhook…
http_response_code(200);
echo json_encode(['status' => 'ok']);

Node.js

const crypto = require('crypto');

function computeWebhookSignature(endpoint, secretKey, payload) {
    let str = endpoint;
    str = dataLoop(str, payload);
    return crypto.createHmac('sha1', secretKey).update(str).digest('base64');
}

function dataLoop(signature, data) {
    const keys = Object.keys(data).sort();
    for (const key of keys) {
        const value = data[key];
        if (value !== null && typeof value === 'object') {
            signature = dataLoop(signature, value);
        } else if (value === null || value === false || value === 0 || value === '') {
            // ignorer les valeurs falsy (null, false, 0, "")
        } else {
            const strValue = value === true ? '1' : String(value);
            signature += key + strValue;
        }
    }
    return signature;
}

function verifyWebhookSignature(endpoint, secretKey, payload, receivedSignature) {
    const computed = computeWebhookSignature(endpoint, secretKey, payload);
    return crypto.timingSafeEqual(
        Buffer.from(computed),
        Buffer.from(receivedSignature)
    );
}

// --- Utilisation avec Express ---

const express = require('express');
const app = express();
app.use(express.json());

app.post('/receive-webhook', (req, res) => {
    const endpoint          = 'https://your-server.com/receive-webhook';
    const secretKey         = 'your-signature-key';
    const receivedSignature = req.headers['x-calltracking-signature'] || '';

    try {
        if (!verifyWebhookSignature(endpoint, secretKey, req.body, receivedSignature)) {
            return res.status(400).json({ error: 'Invalid signature' });
        }
    } catch {
        return res.status(400).json({ error: 'Invalid signature' });
    }

    // Traitement du webhook…
    console.log('Event received:', req.body.event);
    res.status(200).json({ status: 'ok' });
});

app.listen(8080, () => {
    console.log('Webhook receiver running on http://localhost:8080');
});
const crypto = require('crypto');

function computeWebhookSignature(endpoint, secretKey, payload) {
    let str = endpoint;
    str = dataLoop(str, payload);
    return crypto.createHmac('sha1', secretKey).update(str).digest('base64');
}

function dataLoop(signature, data) {
    const keys = Object.keys(data).sort();
    for (const key of keys) {
        const value = data[key];
        if (value !== null && typeof value === 'object') {
            signature = dataLoop(signature, value);
        } else if (value === null || value === false || value === 0 || value === '') {
            // ignorer les valeurs falsy (null, false, 0, "")
        } else {
            const strValue = value === true ? '1' : String(value);
            signature += key + strValue;
        }
    }
    return signature;
}

function verifyWebhookSignature(endpoint, secretKey, payload, receivedSignature) {
    const computed = computeWebhookSignature(endpoint, secretKey, payload);
    return crypto.timingSafeEqual(
        Buffer.from(computed),
        Buffer.from(receivedSignature)
    );
}

// --- Utilisation avec Express ---

const express = require('express');
const app = express();
app.use(express.json());

app.post('/receive-webhook', (req, res) => {
    const endpoint          = 'https://your-server.com/receive-webhook';
    const secretKey         = 'your-signature-key';
    const receivedSignature = req.headers['x-calltracking-signature'] || '';

    try {
        if (!verifyWebhookSignature(endpoint, secretKey, req.body, receivedSignature)) {
            return res.status(400).json({ error: 'Invalid signature' });
        }
    } catch {
        return res.status(400).json({ error: 'Invalid signature' });
    }

    // Traitement du webhook…
    console.log('Event received:', req.body.event);
    res.status(200).json({ status: 'ok' });
});

app.listen(8080, () => {
    console.log('Webhook receiver running on http://localhost:8080');
});
const crypto = require('crypto');

function computeWebhookSignature(endpoint, secretKey, payload) {
    let str = endpoint;
    str = dataLoop(str, payload);
    return crypto.createHmac('sha1', secretKey).update(str).digest('base64');
}

function dataLoop(signature, data) {
    const keys = Object.keys(data).sort();
    for (const key of keys) {
        const value = data[key];
        if (value !== null && typeof value === 'object') {
            signature = dataLoop(signature, value);
        } else if (value === null || value === false || value === 0 || value === '') {
            // ignorer les valeurs falsy (null, false, 0, "")
        } else {
            const strValue = value === true ? '1' : String(value);
            signature += key + strValue;
        }
    }
    return signature;
}

function verifyWebhookSignature(endpoint, secretKey, payload, receivedSignature) {
    const computed = computeWebhookSignature(endpoint, secretKey, payload);
    return crypto.timingSafeEqual(
        Buffer.from(computed),
        Buffer.from(receivedSignature)
    );
}

// --- Utilisation avec Express ---

const express = require('express');
const app = express();
app.use(express.json());

app.post('/receive-webhook', (req, res) => {
    const endpoint          = 'https://your-server.com/receive-webhook';
    const secretKey         = 'your-signature-key';
    const receivedSignature = req.headers['x-calltracking-signature'] || '';

    try {
        if (!verifyWebhookSignature(endpoint, secretKey, req.body, receivedSignature)) {
            return res.status(400).json({ error: 'Invalid signature' });
        }
    } catch {
        return res.status(400).json({ error: 'Invalid signature' });
    }

    // Traitement du webhook…
    console.log('Event received:', req.body.event);
    res.status(200).json({ status: 'ok' });
});

app.listen(8080, () => {
    console.log('Webhook receiver running on http://localhost:8080');
});

Python

import hmac
import hashlib
import base64
from flask import Flask, request, jsonify

def compute_webhook_signature(endpoint: str, secret_key: str, payload: dict) -> str:
    signature_str = endpoint
    signature_str = data_loop(signature_str, payload)
    mac = hmac.new(secret_key.encode('utf-8'), signature_str.encode('utf-8'), hashlib.sha1)
    return base64.b64encode(mac.digest()).decode('utf-8')

def data_loop(signature: str, data) -> str:
    if isinstance(data, list):
        items = [(str(i), v) for i, v in enumerate(data)] 
    else:
        items = sorted(data.items(), key=lambda x: str(x[0])) 

    for key, value in items:
        if isinstance(value, (dict, list)):
            signature = data_loop(signature, value)
        elif value in (None, False, 0, ''):
            pass
        else:
            str_value = '1' if value is True else str(value)
            signature += key + str_value
    return signature

def verify_webhook_signature(endpoint: str, secret_key: str, payload: dict, received_signature: str) -> bool:
    computed = compute_webhook_signature(endpoint, secret_key, payload)
    return hmac.compare_digest(computed, received_signature)

# --- Utilisation avec Flask ---

app = Flask(__name__)

@app.route('/receive-webhook', methods=['POST'])
def receive_webhook():
    endpoint           = 'https://your-server.com/receive-webhook'
    secret_key         = 'your-signature-key'
    received_signature = request.headers.get('X-Calltracking-Signature', '')
    payload            = request.get_json()

    if not verify_webhook_signature(endpoint, secret_key, payload, received_signature):
        return jsonify({'error': 'Invalid signature'}), 400

    # Traitement du webhook…
    print('Event received:', payload.get('event'))
    return jsonify({'status': 'ok'}), 200
import hmac
import hashlib
import base64
from flask import Flask, request, jsonify

def compute_webhook_signature(endpoint: str, secret_key: str, payload: dict) -> str:
    signature_str = endpoint
    signature_str = data_loop(signature_str, payload)
    mac = hmac.new(secret_key.encode('utf-8'), signature_str.encode('utf-8'), hashlib.sha1)
    return base64.b64encode(mac.digest()).decode('utf-8')

def data_loop(signature: str, data) -> str:
    if isinstance(data, list):
        items = [(str(i), v) for i, v in enumerate(data)] 
    else:
        items = sorted(data.items(), key=lambda x: str(x[0])) 

    for key, value in items:
        if isinstance(value, (dict, list)):
            signature = data_loop(signature, value)
        elif value in (None, False, 0, ''):
            pass
        else:
            str_value = '1' if value is True else str(value)
            signature += key + str_value
    return signature

def verify_webhook_signature(endpoint: str, secret_key: str, payload: dict, received_signature: str) -> bool:
    computed = compute_webhook_signature(endpoint, secret_key, payload)
    return hmac.compare_digest(computed, received_signature)

# --- Utilisation avec Flask ---

app = Flask(__name__)

@app.route('/receive-webhook', methods=['POST'])
def receive_webhook():
    endpoint           = 'https://your-server.com/receive-webhook'
    secret_key         = 'your-signature-key'
    received_signature = request.headers.get('X-Calltracking-Signature', '')
    payload            = request.get_json()

    if not verify_webhook_signature(endpoint, secret_key, payload, received_signature):
        return jsonify({'error': 'Invalid signature'}), 400

    # Traitement du webhook…
    print('Event received:', payload.get('event'))
    return jsonify({'status': 'ok'}), 200
import hmac
import hashlib
import base64
from flask import Flask, request, jsonify

def compute_webhook_signature(endpoint: str, secret_key: str, payload: dict) -> str:
    signature_str = endpoint
    signature_str = data_loop(signature_str, payload)
    mac = hmac.new(secret_key.encode('utf-8'), signature_str.encode('utf-8'), hashlib.sha1)
    return base64.b64encode(mac.digest()).decode('utf-8')

def data_loop(signature: str, data) -> str:
    if isinstance(data, list):
        items = [(str(i), v) for i, v in enumerate(data)] 
    else:
        items = sorted(data.items(), key=lambda x: str(x[0])) 

    for key, value in items:
        if isinstance(value, (dict, list)):
            signature = data_loop(signature, value)
        elif value in (None, False, 0, ''):
            pass
        else:
            str_value = '1' if value is True else str(value)
            signature += key + str_value
    return signature

def verify_webhook_signature(endpoint: str, secret_key: str, payload: dict, received_signature: str) -> bool:
    computed = compute_webhook_signature(endpoint, secret_key, payload)
    return hmac.compare_digest(computed, received_signature)

# --- Utilisation avec Flask ---

app = Flask(__name__)

@app.route('/receive-webhook', methods=['POST'])
def receive_webhook():
    endpoint           = 'https://your-server.com/receive-webhook'
    secret_key         = 'your-signature-key'
    received_signature = request.headers.get('X-Calltracking-Signature', '')
    payload            = request.get_json()

    if not verify_webhook_signature(endpoint, secret_key, payload, received_signature):
        return jsonify({'error': 'Invalid signature'}), 400

    # Traitement du webhook…
    print('Event received:', payload.get('event'))
    return jsonify({'status': 'ok'}), 200

Récapitulatif des valeurs ignorées dans la signature

Valeur

Incluse ?

Note

"texte"


123 (entier)

Transmis comme "123"

true

Transmis comme "1"

null

Ignorée

false

Ignorée (équivalent à null)

0

Ignorée (équivalent à null)

"" (vide)

Ignorée (équivalent à null)

objet imbriqué

Récursion, clés triées