All articles

Send call transcripts from any tool: the custom inbound

Wire Zapier, n8n, or your own scripts to deliver call transcripts to Wren — the endpoint, payload format, and request signing explained.

Beyond the built-in recorder connections (Fathom, Fireflies, Granola), Wren can accept call transcripts from anything that can send a signed JSON request — Zapier, n8n, a script, or another product you use.

Create your endpoint

Open Settings → Connected Accounts, select the Recorder tab, and find the Custom inbound card. Click Create endpoint and Wren mints two things:

  • a webhook URL — treat it like a password; it identifies your account
  • a signing secret — shown only once, right then

Copy both into the tool that will send transcripts. If you lose the secret, use Rotate secret on the card: it mints a new secret while keeping the URL exactly the same, so you only update the secret in your tools.

There is one custom endpoint per account. Every tool you wire shares the same URL and secret, and rotating the secret applies to all of them at once.

Signing requests

Every request must carry a signature header:

  • Header: x-wren-signature
  • Value: HMAC-SHA256 of the raw request body, using your signing secret as the key, encoded as hex
sig=$(printf '%s' "$body" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -X POST "$URL" \
  -H "content-type: application/json" \
  -H "x-wren-signature: $sig" \
  -d "$body"

Sign the exact bytes you send — serialize your JSON first, then sign that string. A missing or invalid signature is rejected and nothing is stored.

Payload format

{
  "externalId": "mytool:abc123",       // required — see below
  "title": "Call with Sarah",          // optional; exclusion rules match on it
  "startedAt": "2026-08-11T17:00:00Z", // optional; ISO 8601 or epoch
  "durationMs": 1800000,               // optional; must be > 0 or it's ignored
  "summary": "One-line recap",         // optional
  "participants": [                    // optional
    { "name": "Jacob", "email": "jacob@example.com" }
  ],

  // Exactly ONE of these two carries the transcript:
  "segments": [
    { "speaker": "Jacob", "text": "…", "startMs": 0 },
    { "speaker": "Sarah", "text": "…", "startMs": 12000 }
  ],
  // — or —
  "text": "The full transcript as plain text."
}

Rules the payload must follow:

  • externalId is required and should be prefixed per tool (for example zapier:run-4211). Wren deduplicates on it, and every tool shares the one custom connection — two tools both sending "1" would collide. Keep it under 512 characters.
  • The transcript must actually contain text: an empty segments array, or segments whose text is all whitespace, is rejected the same as a missing transcript.
  • speaker is optional per segment (it defaults to "Unknown"); a plain text transcript is stored as a single segment.

What happens after delivery

Wren acknowledges the request immediately and processes it in the background: the call appears in Sources → Calls shortly after, and gets scored soon after that. Re-sending the same externalId is safe — it's a no-op, so your tool can retry on timeouts.

Your exclusion rules run before anything is stored: push every call and let the rules decide. A call whose title matches an exclusion keyword — or any untitled call, if you've turned that toggle on — is never stored at all.

If a delivery has a formatting problem (missing externalId, no transcript), Wren records the failure and shows it on the Custom inbound card rather than storing a broken call.

Still need help?

Can't find what you're looking for? Email us at support@writewithwren.com and we'll get back to you.

What to include in your message — and which address to use for what.