Aperçu

Les webhooks envoient des notifications en temps réel lorsque des événements surviennent dans votre espace de travail Quo, ce qui permet des intégrations puissantes et des flux de travail automatisés. Configurez des webhooks pour transmettre instantanément les données d’événement à vos applications lorsque des appels se terminent, que des messages arrivent ou que des contacts sont modifiés.

Fonctionnement des webhooks

Notifications déclenchées par des événements : Les webhooks envoient des notifications instantanées quand des événements précis se produisent dans votre espace de travail Quo. Lorsqu’un événement se déclenche, Quo envoie une requête HTTP POST à l’URL que vous avez indiquée avec des données détaillées sur l’événement. Flux de travail typique :
  1. Configurer l’URL du webhook et sélectionner les types d’événements
  2. Quo surveille les événements spécifiés
  3. Quand l’événement se produit, Quo envoie une requête POST avec la charge utile de l’événement
  4. Votre application traite les données de l’événement et répond
  5. Quo consigne l’état de la remise et réessaie au besoin
La configuration des webhooks nécessite des autorisations de propriétaire ou d’administrateur de l’espace de travail. Les paramètres se gèrent uniquement dans les applications Web et de bureau.
Interface de configuration des webhooks :
Formulaire de création de webhook dans les paramètres de Quo

Événements de webhook disponibles

Événements de messagerie

Notifications SMS :
  • message.received: SMS reçu par le numéro de téléphone de l’espace de travail (incluant les pièces jointes)
  • message.delivered: SMS envoyé depuis l’espace de travail et livré avec succès (incluant des médias)
Analyses propulsées par l’IA :
  • call.summary.completed: Résumé d’appel généré par l’IA disponible dans la charge utile de l’événement
  • call.transcript.completed: Transcription complète de l’appel disponible dans la charge utile de l’événement

Événements vocaux

Notifications de statut d’appel :
  • call.ringing : Appel entrant reçu par le numéro de téléphone de l’espace de travail
  • call.completed : Appel terminé (répondu ou non, peut inclure une boîte vocale)
  • call.recording.completed : Enregistrement de l’appel disponible à l’URL fournie

Événements de contacts

Gestion des contacts :
  • contact.updated : Contact créé ou modifié dans l’espace de travail
  • contact.deleted : Contact supprimé de l’espace de travail

Configuration des webhooks

Exigences de configuration

Paramètres requis :
ParamètreExigenceDescription
URLRequisPoint de terminaison du gestionnaire de webhook (HTTPS fortement recommandé en production)
Types d’événementsRequisSélectionnez un ou plusieurs types d’événements à surveiller
RessourcesRequisChoisissez des numéros de téléphone (appels/messages) ou des utilisateurs/groupes (contacts) à surveiller
Paramètres facultatifs :
ParamètreDescription
ÉtiquetteNom descriptif pour identifier et gérer le webhook

Processus de configuration

Pour créer un webhook :
  1. Accédez à SettingsWebhooks dans Quo
  2. Cliquez sur Create webhook
  3. Saisissez l’URL de votre gestionnaire de webhook
  4. Sélectionnez les types d’événements à surveiller
  5. Choisissez les numéros de téléphone ou les ressources de contacts
  6. Ajoutez une étiquette facultative pour l’identification
  7. Enregistrez et testez la configuration

Créer des gestionnaires de webhooks

Exigences du gestionnaire

Spécifications techniques :
  • Accepter les requêtes HTTP POST à votre URL de webhook
  • Traiter le payload d’événement JSON dans le corps de la requête
  • Répondre avec un code d’état HTTP de type 2xx dans les 10 secondes
  • Vérifier la signature du webhook pour des raisons de sécurité
  • Gérer les réessais et les échecs de manière robuste
Gestion des réponses :
  • Succès : Renvoyer un code d’état 2xx (aucun corps de réponse requis)
  • Échec : Une réponse non 2xx déclenche la séquence de réessai de Quo
  • Expiration : Aucune réponse dans les 10 secondes entraîne des réessais
Souplesse de développement : Les gestionnaires de webhook peuvent être créés dans n’importe quel langage de programmation prenant en charge les requêtes et réponses HTTP. Déployez-les sur des plateformes infonuagiques, des serveurs ou des fonctions serverless.

Sécurité et authentification

Vérification de la signature du webhook : Tous les appels de webhook incluent des signatures cryptographiques afin de vérifier l’authenticité et d’empêcher les attaques d’usurpation. Format de l’en-tête de signature :
'openphone-signature': 'hmac;1;1639710054089;mw1K4fvh5m9XzsGon4C5N3KvL0bkmPZSAy
b/9Vms2Qo='
Structure de signature :
<schéma>;<version>;<horodatage>;<signature>
ComposantDescriptionValeur actuelle
schemeAlgorithme de signatureToujours « hmac »
versionVersion de la signatureToujours « 1 »
timestampMoment de génération de la signatureHorodatage Unix
signatureSignature HMAC encodée en base64Hachage SHA-256

Processus de vérification de la signature

Étapes de vérification :
  1. Extraire les composantes de l’en-tête openphone-signature
  2. Préparer les données à signer en concaténant timestamp + "." + payload
  3. Décoder la clé de signature à partir de base64 (disponible dans les détails du webhook)
  4. Calculer HMAC-SHA256 avec la clé décodée et les données à signer
  5. Comparer le résultat avec la signature dans l’en-tête
Exigences importantes :
  • Supprimer tous les espaces et retours de ligne du payload JSON avant la concaténation
  • Utiliser la forme binaire de la clé décodée de base64 pour le calcul HMAC
  • Assurer une correspondance exacte des chaînes pour que la vérification réussisse
Obtenir votre clé de signature :
  1. Allez à la page des détails du webhook dans Quo
  2. Cliquez sur l’icône des points de suspension (⋯) en haut à droite
  3. Sélectionnez « Révéler le secret de signature »
  4. Copiez la clé encodée en base64 pour votre application

Exemples d’implémentation

Gestionnaire de webhook Node.js :
const express = require("express")
const bodyParser = require('body-parser')
const crypto = require('node:crypto');

const app = express()
const port = 8000

app.use(bodyParser.json())

app.post("/", function (req, res) {
  // signingKey provient de « Reveal Signing Secret » dans l'application Quo.
  const signingKey = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='

  // Analyser les champs de l'en-tête openphone-signature.
  const signature = req.headers['openphone-signature']
  const fields = signature.split(';')
  const timestamp = fields[2]
  const providedDigest = fields[3]

  // Calculer les données couvertes par la signature.
  const signedData = timestamp + '.' + JSON.stringify(req.body)

  // Convertir la clé de signature encodée en base64 en binaire.
  const signingKeyBinary = Buffer.from(signingKey, 'base64').toString('binary')

  // Calculer le condensé SHA256 HMAC.
  const computedDigest = crypto.createHmac('sha256',signingKeyBinary)
    .update(Buffer.from(signedData,'utf8'))
    .digest('base64')

  // Vérifier que la signature correspond
  if (providedDigest === computedDigest) {
    console.log(`vérification de signature réussie`)
    // Traiter l'événement webhook ici
  } else {
    console.log(`vérification de signature échouée`)
    return res.status(401).send('Non autorisé')
  }

  res.send({})
});

app.listen(port, function () {
  console.log(`serveur webhook en écoute sur le port ${port}`)
})
Gestionnaire de webhook en Python :
import base64
import hmac
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/', methods=['POST'])
def handle_webhook_call():
    # signingKey provient de « Reveal Signing Secret » dans l'application Quo.
    signing_key = 'R2ZLM2o0bFhBNVpyUnU2NG9mYXQ1MHNyR3pvSUhIVVg='

    # Analyser les champs de l'en-tête openphone-signature.
    signature = request.headers['openphone-signature']
    fields = signature.split(';')
    timestamp = fields[2]
    provided_digest = fields[3]

    # Calculer les données couvertes par la signature en octets.
    signed_data_bytes = b''.join([timestamp.encode(), b'.', request.data])

    # Convertir la clé de signature encodée en base64 en octets.
    signing_key_bytes = base64.b64decode(signing_key)

    # Calculer le condensé SHA256 HMAC.
    hmac_object = hmac.new(signing_key_bytes, signed_data_bytes, 'sha256')
    computed_digest = base64.b64encode(hmac_object.digest()).decode()

    # Vérifier que la signature correspond
    if provided_digest == computed_digest:
        print('vérification de signature réussie')
        # Traiter l'événement webhook ici
    else:
        print('vérification de signature échouée')
        return jsonify({'error': 'Non autorisé'}), 401
        
    return jsonify({})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)
Les versions futures pourraient inclure plusieurs signatures séparées par des virgules. Divisez la valeur de l’en-tête selon les virgules pour gérer plusieurs signatures au besoin.

Meilleures pratiques de sécurité

Protection contre les attaques par rejeu : Mettez en place une validation des horodatages pour prévenir les attaques par rejeu :
  • Comparez l’horodatage de la signature à l’heure actuelle
  • Rejetez les requêtes dont les horodatages dépassent la tolérance acceptable (p. ex., 5 minutes)
  • Chaque appel de webhook génère un horodatage et une signature uniques
  • Les nouvelles tentatives incluent automatiquement de nouveaux horodatages
Mesures de sécurité supplémentaires :
  • Utilisez toujours des URL HTTPS pour les webhooks en production
  • Stockez les clés de signature de façon sécurisée (variables d’environnement, gestion des secrets)
  • Mettez en place une gestion adéquate des erreurs et de la consignation
  • Envisagez de limiter le débit des points de terminaison des webhooks

Gestion des erreurs et relances

Système de nouvelle tentative automatique

Comportement des nouvelles tentatives :
  • Conditions de déclenchement : Codes de réponse non 2xx ou délai d’attente de 10 secondes
  • Stratégie de temporisation : Recul exponentiel avec délais croissants
  • Durée des nouvelles tentatives : Jusqu’à 3 jours de tentatives
  • Échec final : Avis par courriel envoyé au créateur du webhook
Calendrier des nouvelles tentatives :
  • Les premières nouvelles tentatives surviennent rapidement pour minimiser le délai
  • Les délais augmentent de façon exponentielle à chaque tentative
  • Priorise une livraison au plus près de l’heure de l’événement initial
  • Suivi automatique de l’état tout au long du processus

Options de nouvelle tentative manuelle

Gestion des webhooks :
  • Voir l’état de livraison dans les détails du webhook Quo
  • Relancer manuellement les appels webhook ayant échoué en tout temps
  • Les webhooks en échec sont marqués avec l’état « failure »
  • Les relances manuelles réussies mettent à jour l’état à « success »

Notifications d’échec

Lorsque toutes les nouvelles tentatives ont été épuisées :
  • Alerte par courriel envoyée au créateur du webhook
  • Appel de webhook marqué comme échec permanent
  • Détails de l’événement conservés pour examen manuel
  • Possibilité de relancer manuellement une fois les problèmes résolus

Tests et validation

Tests de développement

Options de test en local :
  • Clients HTTP : Utilisez cURL, Postman ou Insomnia pour envoyer des requêtes POST de test
  • URL locales : Testez avec localhost durant le développement
  • Charges utiles simulées : Créez des exemples d’événements JSON conformes au format Quo
  • Tests de signature : Vérifiez la logique de validation HMAC avec des clés de test

Fonctionnalités de test de Quo

Requête de test intégrée :
  1. Accédez à la page des détails du webhook dans Quo
  2. Cliquez sur les points de suspension (⋯) en haut à droite
  3. Sélectionnez « Envoyer une requête de test »
  4. Quo envoie un événement d’exemple à l’URL de votre webhook
  5. Vérifiez la validation de la signature et le traitement de la réponse
Les requêtes de test nécessitent des URL de webhook accessibles publiquement. Les URL de développement local ne fonctionnent pas avec la fonctionnalité de test de Quo.

Test d’événement en direct

Déclencheurs d’événements réels :
  • Configurer un webhook pour un type d’événement précis (p. ex., message.received)
  • Sélectionner votre numéro Quo dans les ressources du webhook
  • Déclencher un véritable événement (envoyer un texto à votre numéro)
  • Surveiller la remise et le traitement du webhook
  • Vérifier le bon fonctionnement de bout en bout
Liste de vérification des tests :
  • Le webhook reçoit correctement les requêtes POST
  • La vérification de la signature fonctionne
  • L’analyse du contenu de l’événement réussit
  • La logique de l’application traite correctement les événements
  • Les réponses d’erreur déclenchent de nouvelles tentatives
  • Les réponses de succès arrêtent la séquence de nouvelles tentatives

Résolution des problèmes

Problèmes courants et solutions

Problèmes de configuration :
  • Vérifiez l’URL du webhook pour confirmer qu’elle est correcte et accessible
  • Vérifiez l’état du webhook pour vous assurer qu’il est activé dans les paramètres
  • Confirmez les types d’événements sélectionnés correctement
  • Validez les ressources (numéros de téléphone/utilisateurs/groupes) pour vous assurer qu’elles sont correctes
Problèmes de réponse :
  • Codes d’état HTTP : Assurez-vous de renvoyer des réponses 2xx pour confirmer le succès du traitement
  • Délai de réponse : Renvoyez les réponses dans le délai d’attente de 10 secondes
  • Gestion des erreurs : Mettez en place des réponses d’erreur adaptées pour faciliter le débogage
Vérification de sécurité :
  • Validation de la signature : Vérifiez le calcul de la signature HMAC
  • Format de la clé : Assurez-vous que la clé de signature est correctement décodée en base64
  • Gestion de l’horodatage : Vérifiez l’extraction et la concaténation de l’horodatage

Outils de débogage

Surveillance des événements :
  • Journal des événements : Affichez l’historique de livraison dans la page des détails du webhook
  • Suivi de l’état : Surveillez les taux de réussite et d’échec
  • Tentatives de nouvelle livraison : Examinez les séquences de réessais automatiques
  • Tests manuels : Utilisez « Send Test Request » pour une validation immédiate
Optimisation des performances :
  • Temps de réponse : Maintenez le traitement sous les 10 secondes
  • Taux d’erreurs : Réduisez les échecs pour limiter la surcharge liée aux réessais
  • Journalisation : Mettez en place une journalisation complète des requêtes et des réponses
  • Surveillance : Configurez des alertes pour les échecs de livraison des webhooks

Exemples de charges utiles d’événement

Événements de messages

Charge utile message.received :
{  
  "id": "EVc67ec998b35c41d388af50799aeeba3e",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-23T16:55:52.557Z",  
  "type": "message.received",  
  "data": {  
    "object": {  
      "id": "AC24a8b8321c4f4cf2be110f4250793d51",  
      "object": "message",  
      "from": "+14155550100",  
      "to": "+13105550199",  
      "direction": "incoming",  
      "body": "Bonjour",
      "media": [  
        {  
         "url": "https://storage.googleapis.com/opstatics-dev/6c908000ada94d9fb206649ecb8cc928",  
         "type": "image/jpeg"  
        }  
      ],  
      "status": "received",  
      "createdAt": "2022-01-23T16:55:52.420Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
Charge utile « message.delivered » :
{  
  "id": "EVdefd85c2c3b740429cf28ade5b69bcba",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-23T17:05:56.220Z",  
  "type": "message.delivered",  
  "data": {  
    "object": {  
      "id": "ACcdcc2668c4134c3cbfdacb9e273cac6f",  
      "object": "message",  
      "from": "+13105550199",  
      "to": "+14155550100",  
      "direction": "outgoing",  
      "body": "Passez une belle journée",
      "media": [  
        {  
         "url": "https://opstatics-dev.s3.amazonaws.com/i/ab6084db-5259-42c0-93c1-e17fb2628567.jpeg",  
         "type": "image/jpeg"  
        }  
      ],  
      "status": "delivered",  
      "createdAt": "2022-01-23T17:05:45.195Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}

Événements d’appels

Charge utile call.ringing :
{  
  "id": "EV95c3708f9112412a834cc8d415470cd8",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-23T17:07:51.454Z",  
  "type": "call.ringing",  
  "data": {  
    "object": {  
      "id": "ACbaee66e137f0467dbed5ad4bc8d60800",  
      "object": "call",  
      "from": "+14155550100",  
      "to": "+13105550199",  
      "direction": "entrant",
      "media": [],  
      "voicemail": null,  
      "status": "sonnerie",
      "createdAt": "2022-01-23T17:07:51.116Z",  
      "answeredAt": null,  
      "completedAt": null,  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
call.completed (appel entrant avec messagerie vocale) :
{  
  "id": "EVd39d3c8d6f244d21a9131de4fc9350d0",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-24T19:22:25.427Z",  
  "type": "call.completed",  
  "data": {  
    "object": {  
      "id": "ACa29ee906a4e04312a6928427b1c21721",  
      "object": "call",  
      "from": "+14145550100",  
      "to": "+13105550199",  
      "direction": "incoming",  
      "media": [],  
      "voicemail": {  
        "url": "https://m.openph.one/static/85ad4740be6048e4a80efb268d347482.mp3",  
        "type": "audio/mpeg",  
        "duration": 7  
      },  
      "status": "completed",  
      "createdAt": "2022-01-24T19:21:59.545Z",  
      "answeredAt": null,  
      "completedAt": "2022-01-24T19:22:19.000Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
call.completed (appel sortant répondu) :
{  
  "id": "EV348de11e4b134fa48017ac45a251dd3e",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-24T19:28:45.370Z",  
      "type": "call.completed",
  "data": {  
    "object": {  
      "id": "AC7ab6f57e62924294925d0ea961de7dc5",  
      "object": "call",  
      "from": "+13105550199",  
      "to": "+14155550100",  
      "direction": "outgoing",
      "media": [],  
      "voicemail": null,  
      "status": "completed",
      "createdAt": "2022-01-24T19:28:33.892Z",  
      "answeredAt": "2022-01-24T19:28:42.000Z",  
      "completedAt": "2022-01-24T19:28:45.000Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}
Charge utile « call.recording.completed » :
{  
  "id": "EVda6e196255814311aaac1983005fa2d9",  
  "object": "event",  
  "apiVersion": "v2",  
  "createdAt": "2022-01-24T19:30:55.400Z",  
  "type": "call.recording.completed",  
  "data": {  
    "object": {  
      "id": "AC0d3b9011efa041d78c864019ad9e948c",  
      "object": "call",  
      "from": "+14155550100",  
      "to": "+13105550199",  
      "direction": "incoming",  
      "media": [  
        {   
          "url": "https://storage.googleapis.com/opstatics-dev/b5f839bc72a24b33a7fc032f78777146.mp3",  
          "type": "audio/mpeg",  
          "duration": 7  
        }  
      ],  
      "voicemail": null,  
      "status": "completed",
      "createdAt": "2022-01-24T19:30:34.675Z",  
      "answeredAt": "2022-01-24T19:30:38.000Z",  
      "completedAt": "2022-01-24T19:30:48.000Z",  
      "userId": "USu5AsEHuQ",  
      "phoneNumberId": "PNtoDbDhuz",  
      "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79"  
    }  
  }  
}

Événements de contact

Contenu de la charge contact.updated et contact.deleted :
{  
  "id": "EVe844e47e9fa4494d9acfa1144839ed94",  
  "object": "event",  
  "createdAt": "2022-01-24T19:44:09.579Z",  
  "apiVersion": "v2",  
  "type": "contact.updated",  
  "data": {  
    "object": {  
      "id": "CT61eeff33f3b14cfe6358cb52",  
      "object": "contact",  
      "firstName": "Jane",  
      "lastName": "Smith",  
      "company": "Comp Inc",  
      "role": "Agent",  
      "pictureUrl": null,  
      "fields": [  
         {  
           "name": "Téléphone",  
           "type": "phone-number",  
           "value": "+14155551212"  
         },  
         {  
           "name": "Courriel",  
           "type": "email",  
           "value": null  
         },  
         {  
           "name": "Prop1",  
           "type": "string",  
           "value": "Value12"  
         }  
      ],  
      "notes": [  
        {  
          "text": "@USu5AsEHuQ ma note 😂",
          "enrichment": {  
            "taggedIds": {   
              "groupIds": [],   
              "userIds": [  
                "USu5AsEHuQ"  
              ],  
              "orgIds": []  
            },  
            "tokens": {   
              "USu5AsEHuQ": {   
                "token": "USu5AsEHuQ",   
                "replacement": "Tom Smith",   
                "type": "mention",   
                "locations": [  
                  {   
                    "startIndex": 1,   
                    "endIndex": 11   
                  }  
                ]   
              }   
            }   
          },  
          "createdAt": "2022-01-24T19:35:38.323Z",  
          "updatedAt": "2022-01-24T19:35:38.323Z",  
          "userId": "USu5AsEHuQ"  
        }  
      ],  
      "sharedWith": [  
        "USu5AsEHuQ"  
       ],  
      "createdAt": "2022-01-24T19:35:38.318Z",  
      "updatedAt": "2022-01-24T19:44:09.565Z",  
      "userId": "USu5AsEHuQ"  
    }  
  }  
}

Événements d’analyse IA

Charge utile de call.summary.completed :
{  
  "id": "EVc86d16fed5314cf6bd7bf13f11c65fd2",  
  "object": "event",  
  "apiVersion": "v3",  
  "createdAt": "2024-09-05T15:30:24.213Z",  
  "type": "call.summary.completed",  
  "data": {  
    "object": {  
      "object": "callSummary",  
      "callId": "AC641569d25e0a4489ad807409d9bee3fd",  
      "status": "completed",  
      "summary": [  
        "voici le résumé de votre appel."  
      ],  
      "nextSteps": [  
        "voici vos prochaines étapes."
      ]  
    }  
  }  
}
call.transcript.completed payload :
{  
  "id": "EV67e3564088bf4badb6593cd7db8f2832",  
  "object": "event",  
  "apiVersion": "v3",  
  "createdAt": "2024-09-05T15:30:18.629Z",  
  "type": "call.transcript.completed",  
  "data": {  
    "object": {  
      "object": "callTranscript",  
      "callId": "AC741569d25e0a4489ad804709d9bee3fc",  
      "createdAt": "2024-09-05T15:30:18.198Z",  
      "dialogue": [  
        {  
          "end": 73.57498,  
          "start": 0.03998,  
          "userId": "UStpTEr9a6",  
          "content": "allô tout le monde",  
          "identifier": "+12345678901"  
        },  
        {  
          "end": 1.52,  
          "start": 1.12,  
          "userId": "USiAYGldYE",  
          "content": "salut",
          "identifier": "+19876543210"  
        }  
      ],  
      "duration": 87.614685,  
      "status": "completed"  
    }  
  }  
}

Ressources supplémentaires

Besoin d’aide ? Soumettez une demande de soutien à support.openphone.com pour obtenir de l’assistance technique avec l’implémentation de webhook.