Skip to content

Add vapi: 10 doc-sourced server-message payloads - #29

Open
garethx wants to merge 2 commits into
hookdeck:mainfrom
garethx:feat/vapi-doc-samples
Open

Add vapi: 10 doc-sourced server-message payloads#29
garethx wants to merge 2 commits into
hookdeck:mainfrom
garethx:feat/vapi-doc-samples

Conversation

@garethx

@garethx garethx commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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 compile passes — vapi publishes, and the provenance check added in #27 is satisfied: every file carries a source key and the version is declared docs, not capture.

topic_identifier is message.type, not a top-level field

Every 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-type only. 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 payloads gap. 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.request conversation-update
knowledge-base-request language-change-detected
phone-call-control speech-update
transcript user-interrupted
voice-input voice-request

Harvested by generator/harvest_vapi.py in the registry repo, from Vapi's <url>.md markdown mirrors via the fetch-sources cache — scripted and re-runnable rather than hand-pasted.

🤖 Generated with Claude Code

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>
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant