Setup and events
Register an endpoint, store the secret, and the three events with the envelope they arrive in.
Register an HTTPS endpoint and we POST to it when something finishes. This is the alternative to polling, and on a busy account it is most of your request budget back.
/api/v1/webhooksscope: shows:write| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Public HTTPS endpoint. Private and link-local addresses are refused, at registration and again at delivery time. |
events | array | No | Which events to receive. Omitted or empty subscribes to all of them. |
description | string | No | For your own benefit in the dashboard. |
Store the secret now. It appears in this response and nowhere else, ever. Without it you cannot verify a delivery, and the only remedy is to delete the endpoint and register a new one.
Managing endpoints
/api/v1/webhooksscope: shows:read/api/v1/webhooks/{id}scope: shows:write/api/v1/webhooks/{id}scope: shows:writeListing also returns the full event catalogue, your endpoint allowance, and each endpoint’s last_error and last_success_at: the fastest answer to “why did we stop receiving these”. There is no separate webhooks:* scope; a subscription is account configuration, so reading needs shows:read and changing needs shows:write.
Events
| Event | Fires when |
|---|---|
transcription.completed | A transcription finishes. A YouTube video with captions completes inline, so this can arrive moments after the 202: look the id up rather than assuming you have stored the job. |
episode.detected | A new episode appears in a connected RSS feed. |
episode.published | An episode is published to a public page. |
The catalogue is deliberately small and closed: adding an event means committing to its payload shape indefinitely.
The envelope
id is the delivery id and is stable across retries: dedupe on it. created_at is when the event happened, not when this attempt was sent. api_version changes only for a breaking change. data carries identifiers rather than content, because a transcript body in every payload is a retry storm: receivers routinely cap bodies at 1 MB and a three-hour episode exceeds that. transcript_url is the stored document and needs no key, so a receiver that cannot authenticate still has something to fetch; for a specific format use GET /api/v1/transcriptions/{id}?format=srt.
Carrying the transcript in the delivery
An endpoint can ask for the transcript inline. This is for a receiver that cannot call the API back — a no-code automation, a chat workflow — and it is off by default. Set it per endpoint, not per transcription: a webhook consumer is code we cannot migrate, and a payload whose shape depended on how each job was submitted would make one endpoint handle several shapes.
The delivery then carries text, text_format and text_bytes alongside the identifiers. format is one of txt, srt, vtt or json — docx and pdf are binary and have no place in a JSON body. Speaker labels are included whenever the transcript has them.
A transcript over 256 KB is left out, never trimmed. The delivery still arrives, with text_omitted set to too_large and text_bytes saying how big it was, so a receiver can decide what to do; half a transcript that does not announce itself as half is worse than none. text_omitted is also no_timings when a time-coded format was asked of a transcript stored as plain text, and unreadable or not_found when the stored document could not be loaded. Only transcription.completed is expanded; the other two events describe episodes.
Rendering happens when the delivery is sent, not when it is queued, so changing an endpoint’s format applies to deliveries already waiting.
| Header | Contains |
|---|---|
X-PTT-Signature | t=<unix>,v1=<hex hmac-sha256> (see Security and delivery). |
X-PTT-Event | The event name, so you can route without parsing the body. |
X-PTT-Delivery | The delivery id, matching id in the envelope. |
User-Agent | PodcastsToText-Webhooks/1.0 |
Receiver checklist
- Respond 2xx within 10 seconds: queue the real work and return. A receiver that works before answering turns every delivery into seven.
- Deduplicate on the envelope
id. At-least-once delivery means you will eventually see a duplicate. - Do not trust the payload without verifying the signature.
- Return 410 Gone when an endpoint is genuinely retired. We stop and disable it rather than retrying for a day.
- Point a throwaway endpoint at a request-inspection service first, to see the headers and the envelope before you write the handler.