WebhooksWebhooks

Webhooks

Get notified instantly when a call completes via webhook events.

Twenty2 sends a webhook event to your server every time a call ends. This is the most reliable way to capture call outcomes, recordings, transcripts, and output variables — without polling the API.


Setup

Go to Integrations

Navigate to Profile → Integrations in your Twenty2 dashboard

Enter your endpoint URL

Find the Webhook URL field and enter your publicly accessible HTTPS endpoint

Save

Click Save — Twenty2 will immediately start sending events to your URL

Twenty2 sends a POST request to your URL every time a call ends. Make sure your endpoint returns a 200 response within 5 seconds.


Events

EventDescription
call.failedCall did not connect or was unanswered
call.completedCall connected and completed successfully

Example Payload

{
  "event": "call.failed",
  "event_id": "evt_dd640228-c82f-494f-b0ff-4ef3db3aaf3b",
  "timestamp": "2026-05-22T07:18:39.684Z",
  "workspace_id": "69bcb7e7cf2a02ecc7a28e17",
  "assistant_id": "6a02dc5d92b9cbce7962a2b5",
  "assistant_name": "Varsha",
  "call": {
    "call_id": "6a100328ba090039ac8610a2",
    "direction": "outbound",
    "status": "failed",
    "from_number": "7971441943",
    "to_number": "9871532305",
    "started_at": "2026-05-22T07:18:30.092Z",
    "answered_at": null,
    "ended_at": "2026-05-22T07:18:39.684Z",
    "duration_seconds": 0,
    "billed_duration_seconds": 0,
    "call_cost_inr": 0,
    "disconnection_reason": "unanswered"
  },
  "campaign": {
    "campaign_id": "6a100328ba090039ac8610a0",
    "campaign_name": "web test"
  },
  "contact": {
    "phone_number": "9871532305",
    "input_variables": {
      "customerName": "Lakshay",
      "productInCart": "Shoes"
    }
  },
  "output_variables": {},
  "recording": {
    "available": false,
    "url": null
  },
  "transcript": {
    "available": false
  }
}

Payload Reference

Root fields

FieldTypeDescription
eventstringEvent type — call.completed or call.failed
event_idstringUnique identifier for this webhook delivery
timestampstringTime the event was fired (ISO 8601)
workspace_idstringYour Twenty2 workspace ID
assistant_idstringID of the assistant that handled the call
assistant_namestringName of the assistant

call object

FieldTypeDescription
call_idstringUnique identifier for the call
directionstringAlways outbound for Phase 1
statusstringFinal call status — completed or failed
from_numberstringNumber the call was made from
to_numberstringNumber the call was made to
started_atstringCall start time (ISO 8601)
answered_atstringTime call was answered — null if unanswered
ended_atstringCall end time (ISO 8601)
duration_secondsnumberTotal call duration in seconds
billed_duration_secondsnumberBillable duration in seconds
call_cost_inrnumberCall cost in INR
disconnection_reasonstringReason call ended — e.g. unanswered, completed

campaign object

FieldTypeDescription
campaign_idstringID of the campaign — null for standalone calls
campaign_namestringName of the campaign

contact object

FieldTypeDescription
phone_numberstringContact's phone number
input_variablesobjectKey-value pairs passed into the call at trigger time

output_variables object

Key-value pairs captured by the assistant during the call. Empty {} if the call did not connect or no variables were captured.

recording object

FieldTypeDescription
availablebooleantrue if a recording is available for this call
urlstringURL to download the recording — null if not available

transcript object

FieldTypeDescription
availablebooleantrue if a transcript is available for this call

Delivery & Retries

If your endpoint fails to return a 200, Twenty2 retries the webhook:

AttemptTiming
1stImmediately after call ends
2nd5 minutes later
3rd30 minutes later

After 3 failed attempts the webhook is dropped. Make sure your endpoint is stable and responds within 5 seconds. Use the Call History API to recover any missed data.


Signature Verification

Every webhook request includes an X-Twenty2-Signature header so you can verify it genuinely came from Twenty2. The signature is an HMAC-SHA256 hash of the raw request body signed with your webhook secret.

    const crypto = require("crypto");

    function verifyWebhook(rawBody, signature, secret) {
      const expected = crypto
        .createHmac("sha256", secret)
        .update(rawBody)
        .digest("hex");

      const expectedHeader = `sha256=${expected}`;
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedHeader)
      );
    }

    app.post("/webhook", (req, res) => {
      const signature = req.headers["x-twenty2-signature"];
      const isValid = verifyWebhook(req.rawBody, signature, process.env.WEBHOOK_SECRET);

      if (!isValid) return res.status(401).send("Invalid signature");

      const event = req.body;
      console.log(`Event: ${event.event}, Call: ${event.call.call_id}`);
      res.status(200).send("OK");
    });

Always use timingSafeEqual or equivalent for signature comparison — never plain string equality. This prevents timing attacks.

Was this page helpful?

Last updated 1 day ago

Built with Documentation.AI