Skip to content

Ask AI

Ask a question about Scribewren, such as uploading a file, editing a transcript, plans or the API.

Ctrl or Cmd+Enter to send

Answers are written by AI and can be wrong. For account or billing help, use the chat or contact us. Contact support (opens in a new tab)

Webhooks

A webhook is an HTTPS call that Scribewren makes to your server when a transcription ends. It replaces polling GET /transcriptions/{id}. Webhooks are part of the API, so they need the Unlimited plan.

The payload is thin on purpose. It carries IDs, the status and a few timestamps, and never the transcript. When you receive an event, call the API to fetch what you need. See Segments and Export.

You add, change, disable and delete webhook endpoints in the app, under Settings → API, with your signed-in session. An API key cannot manage endpoints, and the API host has no routes for it.

  • You can have up to 5 endpoints, whether they are enabled or not.
  • Each endpoint has a URL and a non-empty set of events to subscribe to.
  • The signing secret is shown once, when you create the endpoint and when you rotate its secret. Copy it into your secret manager then. It cannot be shown again.
  • You can send a test event, rotate the secret, disable the endpoint, re-enable it and read the delivery log.

Listing endpoints, reading the delivery log and deleting endpoints keep working if you leave the Unlimited plan, so you can still clean up. Creating, changing, rotating and testing need the plan and an account in good standing.

The URL must:

  • use https on the default port, 443;
  • carry no user name or password;
  • be at most 2048 characters;
  • point to a public host.

Scribewren checks the URL when you save it and again every time it connects. It refuses addresses that are not on the public internet: loopback, private networks, link-local, carrier-grade NAT, cloud metadata, reserved, documentation and multicast ranges, and IPv6 unique-local addresses. It does not use a proxy and it never follows redirects. A URL that fails the check is rejected when you save it, or the delivery fails with lastError set to blocked_address.

An internationalized host name is stored in its punycode form.

Event When
transcription.succeeded A transcription finished successfully.
transcription.failed A transcription ended as failed.
webhook.test You asked for a test event in the app. You cannot subscribe to it.

Nothing is sent when a job is canceled, for the sample transcription, for a banned or deleting account, or when the owner’s plan no longer includes the API.

The body is JSON with three fields: type, timestamp (UTC, ISO 8601) and data.

{
"type": "transcription.succeeded",
"timestamp": "2026-12-01T09:41:07.512Z",
"data": {
"id": "019a4f2e-7c1b-7d3a-9f00-3c5e8a1b2d4f",
"mediaId": "019a4f2d-e5a0-7b42-8c11-6d2f0e9a7b13",
"status": "succeeded",
"language": "en",
"durationMs": 1834000,
"completedAt": "2026-12-01T09:41:06.988Z"
}
}
Field Notes
data.id The transcription ID.
data.mediaId The media ID.
data.status succeeded or failed.
data.language The language code, or null if it was never detected.
data.durationMs Transcribed duration in milliseconds, or null if unknown.
data.completedAt UTC timestamp, or null.
data.errorCode Only on transcription.failed: MEDIA_UNREADABLE, NO_AUDIO_STREAM, INTERNAL_ERROR or WORKER_TIMEOUT.

A webhook.test event has data set to { "endpointId": "..." } and nothing else.

The payload never contains a title, a file name, transcript text or an email address. Do not rely on the order of keys inside the JSON. Parse it as an object.

Each delivery is a POST with a JSON body and these headers:

POST /scribewren HTTP/1.1
Content-Type: application/json
User-Agent: Scribewren-Webhooks/1
webhook-id: msg_019a4f30-1c22-7a10-b5d3-42f1c0a8e777
webhook-timestamp: 1793785267
webhook-signature: v1,EXAMPLESIGNATUREEXAMPLESIGNATUREEXAMPLESIGNATURE=

The three webhook-* headers follow the Standard Webhooks specification, so ready-made libraries can verify them.

Header Meaning
webhook-id msg_ plus a delivery ID. A delivery is one event sent to one endpoint. Every retry of that delivery carries the same webhook-id, so you can use it to deduplicate. Two endpoints subscribed to the same event get different IDs.
webhook-timestamp Unix time in seconds. It is fresh on every attempt.
webhook-signature One or more space-separated signatures, each starting with v1,.

Return any 2xx status within 10 seconds. That marks the delivery as done. Do the real work after you respond, for example by putting the event on a queue.

Anything else counts as a failure and is retried: a timeout, a network error, a redirect, or any non-2xx status. Scribewren never reads your response body.

A delivery is attempted up to 8 times. The waits between attempts are:

Wait before each webhook retry
After attempt Wait before the next
1 1 min
2 5 min
3 30 min
4 2 h
5 5 h
6 10 h
7 10 h

Each wait is randomly shifted by up to 10% so that a recovering server does not get every retry at once. All attempts together span about 28 hours.

Delivery is at least once and unordered. Expect the same delivery twice, and expect events to arrive out of order. Deduplicate on webhook-id, and when order matters, read the current state with GET /transcriptions/{id} instead of trusting arrival order.

Checking whether you have seen a webhook-id and then storing it is two steps. Two retries that arrive together can both pass the check. Let the database decide instead: keep a table with a unique key on webhook_id and claim the ID with one statement.

INSERT INTO processed_webhooks (webhook_id)
VALUES ($1)
ON CONFLICT (webhook_id) DO NOTHING
RETURNING webhook_id;

If the statement returns a row, this is the first time you see the delivery: process it. If it returns no row, you already have it: answer 2xx and do nothing else. Run the insert and your own work in one transaction. If the work fails, the transaction rolls back, the ID is not kept, and the next retry gets to process the delivery. Keep the IDs for longer than the retries span, then delete them.

An endpoint is disabled when 5 deliveries in a row run out of attempts and the first of those is at least 48 hours old. One successful delivery resets the count. Test events never count. A disabled endpoint shows the reason failing.

Fix your server, then re-enable the endpoint in the app. That also resets its failure counters. Changing the URL alone does not re-enable it. A delivery that was waiting when its endpoint was disabled or deleted ends as failed with lastError set to endpoint_disabled, without another request.

When you disable an endpoint yourself, its reason is user.

Choose to send a test event in the app to check your URL and your verification code. It is a normal signed delivery with the type webhook.test. The endpoint must be enabled.

Rotate an endpoint’s secret in the app. The new secret is shown once. For 24 hours the old secret keeps signing too, and webhook-signature carries two v1, signatures, so you can deploy the new secret without dropping events. Verification libraries accept a request if any of its signatures match. A second rotation inside that window ends the overlap for the first secret.

The app shows the last 50 deliveries of an endpoint, newest first. Each row has:

Field Notes
event The event type.
status pending, succeeded or failed.
attempts, maxAttempts Attempts made, and the most there will be.
lastStatusCode The HTTP status of the last attempt, if your server answered.
lastError A short kind, if the last attempt failed: timeout, dns, connect, tls, blocked_address, redirect, http_status or endpoint_disabled.
createdAt, lastAttemptAt, deliveredAt UTC timestamps.

The log keeps no payload, URL, response body or transcription ID. Rows are deleted after 30 days.

Deleting an account disables all of its endpoints and revokes its keys.

Scribewren does not log your endpoint URL, the payload or your response. Log lines contain only IDs and the error kind. Webhook payloads are small by design: you decide what to fetch afterwards, over your authenticated API connection.

Can't find what you need? Contact support, email [email protected], or open Help and then Contact in the app. scribewren.com