Utiliser les données d’une application dans une séquence vocale
Utilisez voice.webhook lorsque votre application détient des données dont l’appel a besoin : l’état d’une commande, une équipe ou la confirmation d’un rendez-vous. L’étape renvoie ces données sur success, puis une étape Branch ultérieure choisit le chemin à partir de celles-ci.
Définir la réponse
Définissez config.output_schema sur un schéma d’objet pour les données renvoyées. L’étape a deux sorties : success transporte des données conformes à ce schéma et failure gère les problèmes techniques. Sans configuration, la sortie est un objet vide.
L’entrée contient une url HTTPS statique et un objet data facultatif. Les données acceptent un objet littéral, $literal ou un $expr portant sur l’objet entier. Les URL dynamiques, les en-têtes personnalisés et les identifiants ne sont pas pris en charge.
Vérifier les requêtes
Bird signe chaque requête, continuations comprises, avec le secret de rappel de votre espace de travail au moyen d’en-têtes Standard Webhooks : webhook-id, webhook-timestamp et webhook-signature. La signature couvre l’ID, l’horodatage et le corps exact. Pendant les 24 heures qui suivent une rotation du secret, webhook-signature contient une entrée v1, par secret actif, si bien que l’un ou l’autre secret permet la vérification. Les appels peuvent mettre environ une minute à signer avec le nouveau secret : continuez donc d’accepter l’ancien jusqu’à la fin de la période de chevauchement. Votre réponse n’est pas signée : Bird valide le certificat TLS de votre récepteur, et la connexion HTTPS authentifie ce qu’il renvoie.
Vérifiez les en-têtes sur le corps brut avec l’appel verify d’une bibliothèque Standard Webhooks avant toute analyse. La méthode webhooks.unwrap des SDK analyse les événements de la plateforme et ne convient pas à ces requêtes. Faites d’abord une rotation du secret sous Webhooks → Direct callbacks, car le secret initial n’est jamais affiché. La signature prouve qu’un détenteur du secret de votre espace de travail a signé la requête. Vos récepteurs de rappel SMS détiennent le même secret et peuvent aussi signer : ne le confiez donc qu’à des récepteurs de confiance. Toute personne autorisée à modifier les séquences de l’espace de travail choisit l’URL et data. Vérifiez que target_url correspond à votre propre URL afin de refuser une requête Bird signée pour un autre récepteur, et autorisez les actions métier dans votre récepteur. L’autorisation du client n’est jamais transmise.
Les requêtes webhook des séquences ne présentent pas de certificat client, même lorsque votre espace de travail impose le mTLS. Configurez le récepteur pour qu’il accepte une connexion sans certificat client et authentifiez plutôt la requête signée.
Comparez le webhook-timestamp signé, un horodatage Unix en secondes, à l’horloge synchronisée d’un serveur. Rejetez les requêtes datées de plus de cinq minutes dans le passé ou dans le futur. Dédupliquez sur webhook-id avant d’exécuter des effets de bord. Si possible, enregistrez l’ID et le résultat de façon atomique avec l’action, puis renvoyez le résultat enregistré pour une requête répétée. Chaque continuation possède son propre ID. Utilisez une clé d’idempotence métier distincte si des requêtes différentes ne doivent pas répéter la même action.
Renvoyer des données
Le récepteur renvoie une réponse de forme fermée :
{
"schema_version": 1,
"type": "outcome",
"output": { "status": "confirmed", "message": "Your appointment is confirmed." }
}output doit correspondre au schéma configuré. La réponse renvoie des données, et non un port, un ID de nœud ou de nouvelles commandes. Une continuation est {"schema_version":1,"type":"continue","url":"https://example.com/voice/next"} sur l’origine canonique de l’URL d’origine.
Pour router selon les données, connectez success à une étape logic.branch avec des cas tels que steps.decision.output.status == 'confirmed'. Déclarez le champ comme une chaîne enum dans le schéma afin que le récepteur ne puisse renvoyer que ces valeurs.
La requête contient l’identité de l’exécution, de la session et de l’appel, l’identité de la séquence admise et du nœud, votre data matérialisé et results. Placez les entrées métier dans data. Un résultat Gather privé direct peut apparaître une seule fois dans results, et les enregistrements finalisés portent un ID lorsqu’il est disponible. Ces observations ne deviennent pas automatiquement des champs d’expression.
Lire la requête reçue par le récepteur
Une exécution de test d’un brouillon envoie cette structure. Les ID et horodatages ci-dessous sont fictifs. La requête réelle utilise l’exécution admise et l’occurrence actuelle du nœud :
{
"schema_version": 1,
"method": "POST",
"target_url": "https://example.com/decision",
"run_id": "vsr_01k5yb7p000000000000000000",
"sequence": {
"id": "vsq_01k5yb7p000000000000000000",
"version_id": null,
"draft_revision": 1,
"entry_node_id": "start",
"mode": "test"
},
"session_id": "vcs_01k5yb7p000000000000000000",
"call": { "id": "vcl_01k5yb7p000000000000000000", "orig": null, "dest": null },
"node": { "id": "decision", "occurrence_id": "00000000-0000-4000-8000-000000000001" },
"data": { "appointment_id": "appointment_123" },
"results": []
}Pour une exécution publiée, version_id identifie la version sélectionnée et draft_revision vaut null. Pour un appel qui a exécuté une définition inline, id, version_id et draft_revision valent tous null. mode peut valoir test ou production. L’origine et la destination utilisent des objets de partie d’appel pouvant être null. Vérifiez la signature avant de vous fier à ces champs d’identité.
Une observation Gather dans results contient type: "gather", node_id, l’UUID command_id, digits et reason. Une observation d’enregistrement contient type: "recording", l’UUID command_id, state et un recording_id lorsqu’il est disponible. Utilisez les chiffres privés uniquement pour la requête prévue : ils ne deviennent pas des sorties d’expression conservées. Les requêtes de continuation conservent la même occurrence de nœud, et les observations déjà transmises sont retirées de results.
Gérer les échecs
Un échantillon technique utilise le port failure avec {"code":"webhook_unavailable"} ou {"code":"webhook_invalid_response"}. Un échec connecté suit le chemin de reprise, tandis qu’un échec non connecté fait échouer l’exécution. Un success non connecté termine l’exécution normalement. Les requêtes webhook réelles ont un délai de trois secondes et ne font l’objet d’aucune nouvelle tentative automatique. L’aperçu n’envoie aucune requête.
Le délai de trois secondes inclut la lecture du secret de signature. Connectez failure à un chemin de reprise pour que l’appel puisse continuer lorsque le récepteur est indisponible ou renvoie une réponse invalide. Une requête expirée peut déjà avoir déclenché un effet de bord chez le récepteur.
Traitez data, l’identité de l’appel et les chiffres Gather comme des données privées. Restreignez l’accès au récepteur et au secret, évitez de journaliser les corps de requête ou les secrets, et ne conservez les saisies de l’appelant que le temps nécessaire à votre application. Ne placez ni données privées ni identifiants dans les URL : la trace de l’appel conserve l’URL complète, y compris sa chaîne de requête. L’URL et les données configurées ainsi que la sortie validée sont conservées dans l’historique d’exécution. Les chiffres Gather transitoires sont exclus de l’entrée d’étape capturée. Ne renvoyez que les données dont la séquence a besoin.
Lisez les données renvoyées dans steps.decision.output et les erreurs techniques dans steps.decision.error.code. Si les deux chemins se rejoignent, vérifiez l’enveloppe avec has(steps.decision.output) ou has(steps.decision.error) avant de l’utiliser. Un HTTP 200 renvoyé par votre récepteur ne garantit pas un résultat valide tant que son corps ne respecte pas le contrat.
Ressources associées
Poursuivez avec la documentation, les guides et les exemples sur ce sujet.