---
title: "Use ThunderPhone with n8n"
description: "Install the ThunderPhone node for n8n, connect a server API key, and build a workflow that runs every time a call ends."
---

[n8n](https://n8n.io) is a workflow automation tool. The ThunderPhone package
for n8n adds two nodes:

- **ThunderPhone Trigger** starts a workflow when a call ends (**Call
  Completed**) or a call grading run finishes (**Call Graded**).
- **ThunderPhone** places outbound calls, adds contacts to campaigns and
  starts them, gets calls with their transcript and recording, and lists your
  agents and phone numbers.

It is a verified community node, listed at
[ThunderPhone on n8n](https://n8n.io/integrations/thunderphone/).

## Install the node

In the n8n editor, click **+** to open the nodes panel and search for
**ThunderPhone**. An instance owner may need to set up verified nodes before
other users can add them.

On self-hosted n8n, you can also install by package name: open
**Settings > Community Nodes** and install `n8n-nodes-thunderphone`. Check community-node risks and your instance policy
before you install.

## Create a server API key

<Steps>
  <Step title="Open the Keys tab">
    In ThunderPhone, go to **Organization → Keys** and click **Create key**.
  </Step>
  <Step title="Name it">
    Use a name that says where the key lives, such as "n8n".
  </Step>
  <Step title="Copy it now">
    The full `sk_live_...` secret is shown once. Copy it before you close the
    dialog.
  </Step>
</Steps>

Only an admin or owner can create the key, and ThunderPhone treats requests
made with it as coming from that person. The trigger and the campaign
operations need that person to keep the admin or owner role.
[Create a server API key](/guides/api-keys) covers rotation and revocation.

## Add the credential in n8n

1. In n8n, create a credential of type **ThunderPhone API**.
2. Paste the key into **API Key**.
3. Save it and run the credential test. The test reads one call from your
   organization with `GET /v1/calls?limit=1`.

## Build a first workflow

This workflow adds a row to a Google Sheet every time a call ends.

<Steps>
  <Step title="Add the trigger">
    Add **ThunderPhone Trigger**, select your ThunderPhone API credential, and
    set **Event** to **Call Completed**.
  </Step>
  <Step title="Get the call">
    Add a **ThunderPhone** node. Set **Resource** to **Call**, **Operation**
    to **Get**, and **Call ID** to the expression `{{ $json.data.call_id }}`.
    The output is the [call record](/api-reference/calls) plus `transcripts`
    and `recording_url`, a short-lived link that is `null` when the call has
    no recording.
  </Step>
  <Step title="Append a row">
    Add a Google Sheets node that appends a row, and map the columns you want
    from the Get Call output (see the table below).
  </Step>
  <Step title="Activate the workflow">
    Save and activate. The trigger registers its Production URL as a webhook
    endpoint in your ThunderPhone organization. Deactivating the workflow
    deletes that endpoint.
  </Step>
  <Step title="Test it">
    Open an agent in the builder, click **Talk**, and end the call. Builder
    test calls are delivered as `web.complete`, one of the two events behind
    Call Completed, so the workflow runs and a row appears.
  </Step>
</Steps>

Useful columns:

| Column | Expression |
|---|---|
| Call ID | `{{ $json.call_id }}` |
| Direction | `{{ $json.direction }}` |
| From | `{{ $json.from_number }}` |
| To | `{{ $json.to_number }}` |
| Started | `{{ $json.start_time }}` |
| Duration (s) | `{{ $json.duration_seconds }}` |
| Status | `{{ $json.status }}` |
| End reason | `{{ $json.end_reason }}` |
| Transcript | `{{ $json.transcripts.filter(t => typeof t.content === 'string').map(t => t.role + ': ' + t.content).join('\n') }}` |

The recording URL expires. Store the call ID and run Get Call again when you
need a fresh link.

<Note>
  ThunderPhone delivers events with `POST` to the trigger's **Production
  URL**, so your n8n instance must be reachable over HTTPS from the internet,
  and the workflow must be active. The trigger verifies the
  `X-ThunderPhone-Signature` HMAC on every request and answers `401` to
  anything unsigned or invalid.
</Note>

The trigger outputs the complete event: `type`, `event_id` and `data`. See
[`telephony.complete` / `web.complete`](/webhooks/call-complete) and
[`call.graded`](/webhooks/events) for every field. Delivery is at least once,
and `event_id` stays the same across retries of one event, so check it before
a step that must not repeat.

## Place outbound calls safely

**Place Outbound Call** (Resource **Outbound Call**, Operation **Place**)
takes:

| Field | Meaning |
|---|---|
| Agent Name or ID | A saved agent. The dropdown lists your agents. |
| Phone Number Name or ID | The from number. The dropdown lists only numbers enabled for outbound calls. |
| To Number | Destination in E.164 format, e.g. `+14155550199`. |
| Variables | JSON object of values for placeholders in the agent prompt. |
| Idempotency Key | A stable ID from the upstream record or event. Required. |

Map the **Idempotency Key** to an ID from whatever caused the run, such as a
CRM record ID or the trigger's `event_id`. The node sends
`n8n:<node ID>:<your value>`, so:

- An automatic retry or **Retry execution** sends the same key, and
  ThunderPhone returns the original call with `idempotent_replay: true`
  instead of dialing again.
- Two ThunderPhone nodes fed the same record send different keys.
- A blank value fails before any request is sent.

Avoid timestamps, random values, and n8n execution or workflow IDs. A new
value on retry places a second real call. The whole key can be at most 128
characters. Reusing a key with a different agent, number or variables fails
with a conflict.

**Campaigns.** **Add Contacts** (Resource **Campaign**) adds contacts to a
draft campaign. It takes a Campaign ID and a JSON array of contacts. Each contact needs `phone_number`; other keys
become call variables. The node fails when every contact is rejected and sets
`partial_success: true` when only some are. **Start** requires the checkbox
confirming that the campaign places real calls and spends ThunderPhone
credits. See [Outbound campaigns](/guides/outbound-campaigns).

Before a workflow starts calls:

- If ThunderPhone asks your organization to confirm that its outbound calls
  follow the TCPA and other calling laws, an admin must record that
  confirmation in the dashboard first.
- The from number must be enabled for outbound calls. Numbers you bring from
  your own carrier qualify once verified. ThunderPhone numbers start
  inbound-only and can be approved for outbound use: an admin requests it
  from the Phone Numbers page. See
  [Get a phone number](/guides/get-a-phone-number).
- Your organization is responsible for having consent to call each number
  and for following the calling laws that apply. See
  [Outbound calling laws](/guides/outbound-calling-laws).

## Example workflows

- **CRM contacts to a campaign.** Add CRM contacts to a draft ThunderPhone
  campaign, then start the campaign once someone approves the list.
- **Completed call to the customer record.** On Call Completed, run Get Call
  and save the transcript and recording link to the matching customer record.
- **Low grades to review.** On Call Graded, filter on
  `{{ $json.data.grade.score }}` and route low-scoring calls to a review queue
  with the call ID and grade details. A call can be graded more than once,
  and each finished grading run fires the trigger, so keep only the latest
  `graded_at` for each call ID.

<Note>
  Sending the same call events to your own server instead? See
  [Webhook endpoints](/webhooks/endpoints).
  [Place outbound calls](/guides/place-outbound-calls) covers the API behind
  Place Outbound Call.
</Note>
