New: Import recordings from Google Drive, OneDrive, Zoom, Dropbox & Box. First 3 imports free.

Import a recording
Pepys

API & MCP

Webhooks

Get a signed POST the moment a job finishes, with a constant-time HMAC check on your side.

In short

Register an endpoint in Settings → Webhooks, or with POST /api/v1/webhooks, to receive a signed POST on transcription.completed and transcription.failed. Each delivery carries Pepys-Signature, an HMAC-SHA256 of {timestamp}.{rawBody}. Respond 2xx within 10 seconds; failures retry with backoff.

Register an endpoint

Add the URL under Settings → Webhooks, or create it programmatically with POST /api/v1/webhooks. Pepys then posts to it whenever a job reaches a terminal state – no polling loop required.

The two events are transcription.completed and transcription.failed. The payload carries a summary of the job; fetch the full transcript from Get a transcription when you need the text and segments.

Delivery
POST https://your-server.com/webhooks/pepys
Pepys-Event-Type: transcription.completed
Pepys-Event-Id: <jobId>.done
Pepys-Signature: t=1718800000,v1=<hmac-sha256-hex>

{
  "id": "<jobId>.done",
  "type": "transcription.completed",
  "created": 1718800000,
  "data": { "transcription": { "id": "…", "status": "done", "url": "https://pepys.co/api/v1/transcriptions/…" } }
}

Verify the signature

Recompute HMAC-SHA256 over the timestamp, a literal dot, and the raw request body – {timestamp}.{rawBody} – using your endpoint's secret, then compare it to the v1 value from the header with a constant-time comparison. An optional replay window, rejecting deliveries whose timestamp is more than five minutes old, is a sensible extra check.

Node
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const ok = v1.length === expected.length &&
    timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
  if (!ok) throw new Error("bad signature");
  // Optional: reject if Math.abs(Date.now()/1000 - Number(t)) > 300 (replay window).
  return JSON.parse(rawBody);
}

Retries and timeouts

Respond with a 2xx within 10 seconds. A non-2xx response or a timeout is retried with backoff, up to six attempts, so keep the handler fast: acknowledge the delivery, then do the slow work asynchronously on your side.

Webhooks – questions, answered

How do I register a webhook?

Add the endpoint URL in Settings → Webhooks, or create it with POST /api/v1/webhooks. Pepys then posts to it on transcription.completed and transcription.failed.

How do I verify a webhook came from Pepys?

Recompute HMAC-SHA256 over the timestamp, a dot, and the raw request body using your endpoint secret, then compare it to the v1 value in the Pepys-Signature header with a constant-time comparison.

What happens if my endpoint is down?

A non-2xx response or a timeout is retried with backoff, up to six attempts. Respond 2xx within 10 seconds and move slow work off the request path.

Does the webhook payload contain the whole transcript?

It carries a summary of the job. Fetch the full transcript from GET /api/v1/transcriptions/{id} when you need the text and segments.

Which events can I subscribe to?

transcription.completed and transcription.failed.

Related

Try it on your own audio

Create a free account and transcribe a recording – 60 minutes are included, no card required. Still stuck? Email contact@pepys.co or see the support page.