Agent skill
Speechmatics Webhooks Skill
Receive and authenticate Speechmatics batch job notifications (webhooks). Use when setting up a Speechmatics notification_config callback, checking the auth_headers value, debugging 401s on deliveries, or handling the success, error, fetch_error and trim_error job statuses.
Install this skill
npx skills add hookdeck/webhook-skills --skill speechmatics-webhooks
Speechmatics calls the URL in a batch job's notification_config when the job finishes. The request is not signed: you authenticate it with a header you add per job through auth_headers. The job ID and outcome arrive as query parameters, and the body is whatever contents you requested (JSON, plain text, or multipart), or empty.
When to Use This Skill
- How do I receive Speechmatics webhooks (batch job notifications)?
- How do I verify a Speechmatics webhook? (There's no signature: check your
auth_headersvalue) - How do I use
notification_configandauth_headers? - How do I handle the
success,error,fetch_errorandtrim_errorstatuses? - Why is my Speechmatics callback endpoint returning 401 or 413 and being retried?
Verification (core)
Add a secret header to the job, e.g. "auth_ (Authorization: Bearer <token> works too). Speechmatics sends it verbatim. Compare the whole header value in constant time, without parsing off a Bearer prefix:
const crypto = require('crypto');
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest(); // equal length: never throws
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifySpeechmaticsRequest(headers, config = process.env) {
const name = (config.SPEECHMATICS_WEBHOOK_HEADER || 'Authorization').toLowerCase();
const expected = config.SPEECHMATICS_WEBHOOK_SECRET; // the exact value, e.g. "Bearer abc123"
if (!expected) return false; // never accept when unconfigured
const received = headers[name]; // Node lowercases header names
return typeof received === 'string' && safeEqual(received, expected);
}
Return 401 when this fails. A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp), so serve the endpoint over HTTPS and rotate the credential if it leaks. The vendor's own origin check is an IP allowlist, an optional extra.
The body isn't signed, so don't assume JSON: it can be JSON, plain text, multipart, or empty. Authenticate before reading it (Express: express.raw({ type: '*/*' }) after the check).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
There is no event-type field. Speechmatics appends ?id=<job id>&status=<status> to your URL (for example POST /webhooks/speechmatics?id=r6sr3jlzjj&status=success):
status | Sent When |
|---|---|
success | The job was processed successfully |
error | Something went wrong processing the job (outputs that aren't available are left out) |
fetch_error | Speechmatics failed to fetch the data file from a URL |
trim_error | Speechmatics failed to process the audio file before transcription |
These are not the same as the job status values (running, done, ...). The user agent is Speechmatics-API/2.0.
Body by contents
Requested contents | Body |
|---|---|
one of transcript, transcript.json-v2, jobinfo | application/json |
one of transcript.srt, transcript.txt | text/plain |
| two or more items | multipart/form-data |
data alone | multipart/form-data per the notifications page; a raw body such as application/octet-stream per the API reference, so branch on Content-Type |
[] (empty list) | empty: just the job ID and status in the query |
| field left out | the API reference says the transcript, as a data_file attachment; the notifications page says no body, so always set contents |
Delivery Behavior
The method is POST unless the job sets "method": "put", so accept both. Return a 2xx once received: Speechmatics keeps retrying any other response (including 401 and 413) until its timeout, so make sure your body size limit fits the media you request. Callbacks run in parallel and may arrive in any order.
Environment Variables
SPEECHMATICS_WEBHOOK_HEADER=Authorization # the header name you put in auth_headers
SPEECHMATICS_WEBHOOK_SECRET="Bearer replace-with-a-long-random-token" # the exact whole value
Local Development
Speechmatics can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 speechmatics --path /webhooks/speechmatics
Use the printed URL as the url in notification_config.
Resources
- references/overview.md - How notifications work, statuses, body formats, retries
- references/setup.md - Configure
notification_configandauth_headerson a job - references/verification.md - Header check, limits of a static credential, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests