Add vapi: 10 doc-sourced server-message payloads - #29
Open
garethx wants to merge 2 commits into
Open
Conversation
Vapi is a new Hookdeck source (voice-AI agents; its webhook endpoint is the "Server URL"). Skill shipped in hookdeck/webhook-skills#178, registry row in hookdeck/webhook-registry#12. `topic_identifier` is `message.type`, not a top-level field. Every Vapi server message arrives as {"message": {"type": ...}} with no top-level type at all, so a consumer keying off the top level finds nothing. A Vapi CLI tutorial page shows a flatter shape; that is example code rather than the wire format. Marked `sourced_via: "docs"` per hookdeck#26. Headers are the synthesized minimum — content-type only. Vapi has NO fixed signature scheme (authentication is opt-in and per-endpoint, and its HMAC option lets the customer choose algorithm, header name, timestamp header and payload format with no documented defaults), so there is no signature or topic header to synthesize. Inventing one would describe a delivery this vendor does not specify. 10 of the 21 documented message types, not all 21. The other 11 elide an object behind a placeholder comment: { "message": { "type": "status-update", "call": { /* Call Object */ }, ... } } Removing the comment parses and leaves "call": {}, a body asserting Vapi sends an empty call object. These files are sendable mocks, so an invented shape is worse than an absent one. The 11 are named in hookdeck/webhook-registry#12 and recorded there as a partial payloads gap; real captures would resolve them, and a capture would supersede these files anyway. `yarn compile` passes: vapi publishes, and the provenance consistency check added in hookdeck#27 is satisfied — every file here carries a `source` key and the version is declared `docs`, not `capture`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@garethx is attempting to deploy a commit to the Hookdeck Team on Vercel. A member of the Team first needs to authorize it. |
Real deliveries from a Vapi sandbox assistant through a Hookdeck source on 2026-08-12, captured byte-exact from the wire. `status-update` and `end-of-call-report` — both among the 11 message types the docs could NOT supply, because their examples elide the Call Object behind a placeholder comment. These carry it in full: 21 keys on end-of-call-report, with artifact, costBreakdown, performanceMetrics and the real call object. Redacted by generator/redact_capture.py in the registry repo, not by hand. Every UUID is swapped for a well-formed fake through one stable mapping shared across both files, so identifiers that correlate in a real delivery still correlate here; the two call ids differ because these are two different calls, which the mapping preserved rather than flattened. 56 values redacted, and the script then audits its own OUTPUT for surviving identifiers rather than trusting its intent. One redaction was not on the list supplied with the captures, and it was the one that mattered most: `assistant.server.url` is the LIVE INGEST ENDPOINT the webhook was delivered to. It is not personal data, so reading the payload for personal data misses it — but publishing it lets anyone POST arbitrary bodies into that source. Transcripts were emptied too; on these calls they held only "AI: Hello," because the calls errored before any customer audio, but the rule should not depend on that. Headers are the observed set with placeholder values. `x-signature` is an HMAC over the RAW body and the body here is redacted, so the real digest no longer verifies — shipping it would teach a verification that fails, which is worse than shipping none. The header NAMES are what carry the information, and they are exact: x-signature, x-timestamp, x-vapi-secret (present but empty when an HMAC credential is used), x-call-id, user-agent: axios/1.8.3. `provenance` for `latest` is now removed rather than set. The version holds 10 doc-sourced files and 2 captures, and sourced_via has no value for that: `docs` would misdescribe the captures, `capture` would misdescribe the other ten and fail the hookdeck#27 build check, and Vapi has no version scheme to split them into without inventing one. Undeclared is what this repo already does for its 8 other mixed versions, and the per-file `source` key still distinguishes them exactly. A `provenance_note` says so in place of a wrong declaration. `yarn compile` passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vapi is a new Hookdeck source — a voice-AI agent platform whose webhook endpoint is the Server URL. Skill shipped in webhook-skills#178; registry row in webhook-registry#12.
yarn compilepasses — vapi publishes, and the provenance check added in #27 is satisfied: every file carries asourcekey and the version is declareddocs, notcapture.topic_identifierismessage.type, not a top-level fieldEvery Vapi server message arrives as:
{ "message": { "type": "transcript", "role": "user", "transcript": "Hello" } }There is no top-level type at all, so a consumer keying off the top level finds nothing. (A Vapi CLI tutorial page shows a flatter shape — that is example code, not the wire format.)
Headers are the synthesized minimum
content-typeonly. Vapi has no fixed signature scheme: authentication is opt-in and per-endpoint, and its HMAC option lets the customer choose the algorithm, the header name, whether a timestamp header is sent, and the payload format — with no documented defaults for any of them. So there is no signature or topic header to synthesize here, and inventing one would describe a delivery this vendor does not specify.10 of 21, deliberately
The other 11 documented examples elide an object behind a placeholder comment:
{ "message": { "type": "status-update", "call": { /* Call Object */ }, "status": "ended" } }Removing the comment parses — and leaves
"call": {}, a body asserting Vapi sends an empty call object. These files are sendable mocks; someone will POST one at a handler and reason about the shape from it, so an invented shape is worse than an absent one.The 11 are named in webhook-registry#12 and recorded there as a partial
payloadsgap. Real captures would resolve them — and would supersede these files anyway, since a capture beats a doc example.What is here
assistant.speechStarted-style informational events plus the request/response ones:call.endpointing.requestconversation-updateknowledge-base-requestlanguage-change-detectedphone-call-controlspeech-updatetranscriptuser-interruptedvoice-inputvoice-requestHarvested by
generator/harvest_vapi.pyin the registry repo, from Vapi's<url>.mdmarkdown mirrors via the fetch-sources cache — scripted and re-runnable rather than hand-pasted.🤖 Generated with Claude Code