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 :
- Configurer l’URL du webhook et sélectionner les types d’événements
- Quo surveille les événements spécifiés
- Quand l’événement se produit, Quo envoie une requête POST avec la charge utile de l’événement
- Votre application traite les données de l’événement et répond
- 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 :
Événements de webhook disponibles
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
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
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ètre | Exigence | Description |
---|
URL | Requis | Point de terminaison du gestionnaire de webhook (HTTPS fortement recommandé en production) |
Types d’événements | Requis | Sélectionnez un ou plusieurs types d’événements à surveiller |
Ressources | Requis | Choisissez des numéros de téléphone (appels/messages) ou des utilisateurs/groupes (contacts) à surveiller |
Paramètres facultatifs :
Paramètre | Description |
---|
Étiquette | Nom descriptif pour identifier et gérer le webhook |
Processus de configuration
Pour créer un webhook :
- Accédez à Settings → Webhooks dans Quo
- Cliquez sur Create webhook
- Saisissez l’URL de votre gestionnaire de webhook
- Sélectionnez les types d’événements à surveiller
- Choisissez les numéros de téléphone ou les ressources de contacts
- Ajoutez une étiquette facultative pour l’identification
- 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>
Composant | Description | Valeur actuelle |
---|
scheme | Algorithme de signature | Toujours « hmac » |
version | Version de la signature | Toujours « 1 » |
timestamp | Moment de génération de la signature | Horodatage Unix |
signature | Signature HMAC encodée en base64 | Hachage SHA-256 |
Processus de vérification de la signature
Étapes de vérification :
- Extraire les composantes de l’en-tête
openphone-signature
- Préparer les données à signer en concaténant
timestamp + "." + payload
- Décoder la clé de signature à partir de base64 (disponible dans les détails du webhook)
- Calculer HMAC-SHA256 avec la clé décodée et les données à signer
- 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 :
- Allez à la page des détails du webhook dans Quo
- Cliquez sur l’icône des points de suspension (⋯) en haut à droite
- Sélectionnez « Révéler le secret de signature »
- 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 »
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
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 :
- Accédez à la page des détails du webhook dans Quo
- Cliquez sur les points de suspension (⋯) en haut à droite
- Sélectionnez « Envoyer une requête de test »
- Quo envoie un événement d’exemple à l’URL de votre webhook
- 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 :
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
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
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"
}
}
}
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"
}
}
}
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"
}
}
}
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.