Graniite Docs
Recipes

Twilio call recording to Graniite

Wire a Twilio voice number so every recorded call lands in your knowledge base 30 seconds after the hangup.

Record a Twilio call. Within about 30 seconds, the transcript is searchable in your Graniite library and any AI client connected via MCP can quote it.

The whole pipeline is one HTTP Request node in n8n.

What you are building

[Twilio call ends, recording finalizes]
        |
        v
[Twilio "recording-status-callback" webhook fires]
        |
        v
[n8n Webhook node receives RecordingUrl + metadata]
        |
        v
[n8n HTTP Request: POST /api/v1/ingest with kind=url]
        |
        v
[Graniite fetches the recording, AssemblyAI transcribes, embeddings written]
        |
        v
[About 30 seconds later: searchable in Graniite and every MCP client]

Prerequisites

  • A Twilio account with at least one voice number, configured to record calls.
  • n8n (cloud or self-hosted). Most no-code trigger systems work identically. Zapier, Make, and Pipedream all have webhook and HTTP Request equivalents.
  • A Graniite token. Mint at /settings/integrations:
    • Capabilities: read and write
    • Access: pick a specific folder such as Calls if you want all recordings auto-filed there. The scope-size-1 rule means new items will land in that folder automatically.
    • Auto-revoke after: 90 days
  • Save the lev_… token in n8n: Credentials → New → Header Auth, name Graniite, header name Authorization, header value Bearer lev_….

Step 1: n8n webhook node

In n8n, create a new workflow:

  1. Add a Webhook node as the trigger.

  2. HTTP Method: POST

  3. Path: any unique slug (for example, twilio-calls-to-graniite).

  4. Save and activate the workflow. n8n shows you the production webhook URL, something like:

    https://your-n8n-host/webhook/twilio-calls-to-graniite

    Copy it.

Step 2: configure Twilio's recording-status callback

In the Twilio console:

  1. Go to Phone Numbers → Active numbers and click your number.
  2. Scroll to Voice & Fax.
  3. A call comes in: set to whatever you use today (TwiML bin, Studio flow, and so on). This is not what we are changing.
  4. Recording status callback URL: paste the n8n webhook URL from Step 1.
  5. Recording status callback events: check completed. We only care once the recording has finalized in Twilio's storage.
  6. Save.

Twilio will now POST to your n8n webhook every time a recording finalizes for that number. The payload includes RecordingUrl, RecordingSid, CallSid, and RecordingDuration.

Step 3: n8n HTTP Request to /ingest

Right after the Webhook node, add an HTTP Request node:

FieldValue
MethodPOST
URLhttps://graniite.co/api/v1/ingest
AuthenticationGeneric Credential Type, Header Auth, your Graniite credential
Body Content TypeJSON
Specify BodyUsing JSON

For the JSON body:

{
  "kind": "url",
  "url": "{{ $json.body.RecordingUrl }}",
  "in_kb": true,
  "auto_transform": false
}

{{ $json.body.RecordingUrl }} is n8n expression syntax. It pulls the URL out of the Twilio webhook payload that n8n passed to this node. The .body part is n8n's wrapper for webhook payloads. Remove it if you are testing the node with a Manual Trigger instead.

Save the workflow.

Step 4: test with a real call

  1. Call your Twilio number from your phone.
  2. Leave a short voicemail or have a brief conversation.
  3. Hang up.
  4. In the n8n Executions tab, within about 10 seconds you should see a green successful execution.

Inspect the HTTP Request output. You should see:

{
  "status": "transcribing",
  "content_id": "uuid",
  "user_item_id": "uuid",
  "folder_id": "uuid or null"
}

status: "transcribing" means AssemblyAI has accepted the URL and is processing. Wait about 30 seconds. Visit graniite.co/feed. The new item appears with the transcript ready to read.

(Optional) Step 5: wait for transcription before continuing

If your n8n flow needs to gate on transcription finishing (for example, to trigger a Slack notification with the transcript text), add a Wait and HTTP Request loop after the ingest node:

[HTTP: POST /api/v1/ingest]   returns { status: "transcribing", user_item_id }
        |
        v
[Wait 30 seconds]
        |
        v
[HTTP: GET /api/v1/items/{{ $json.user_item_id }}]
        |
        v
[IF: $json.transcription_status === "done"]
   |
   +--- yes: Continue
   |
   +--- no: Loop back to Wait

For short calls (under 3 minutes), you will usually be done after the first poll. Long calls can take 2 or 3 polls.

Trade-offs and gotchas

Twilio media URL auth

Twilio's RecordingUrl is signed and accessible without auth for a window after the call. If your n8n run is delayed, the URL may return 401. In practice the webhook fires within seconds and Graniite fetches immediately, so this rarely bites. If it does, the more robust path is to download the recording from Twilio first (using their API credentials in n8n), re-host the file somewhere with a stable URL (S3, Drive), and ingest that URL. Alternatively, use the binary upload flow.

Counts against your monthly transcription quota

Each call is paid transcription via AssemblyAI. A 30-minute call costs approximately 30 minutes of quota. See your remaining quota at /settings under Usage.

One token per integration

Do not reuse the same lev_… between this Twilio workflow and your Cursor connector. Revoking the Twilio token if Twilio leaks should not also break your AI editing setup.

Idempotency

Twilio occasionally retries the webhook (for example, on transient n8n errors). If you do not want duplicate user_item rows for the same call, either:

  • Use a single "Calls" folder and accept the rare duplicate. This is cheap because the contents row dedupes (the URL is the same) and only the user_items row duplicates.
  • Or check Graniite for an existing item with the same source URL before ingesting (one extra GET /api/v1/items?folder_id=... call).

What this unlocks

  • Ask Claude, Cursor, or ChatGPT "What did Sarah say about the Q3 budget on yesterday's call?" MCP search hits the transcript.
  • Pipe call transcripts into the bulk import recipe's search index for full-text grep over them.
  • Combine with Drive sync so call notes and your written meeting notes live in the same library.

On this page