Developers

End-of-call webhook

When a call ends, the voice agent sends your server a signed JSON with the call data, the contact and the extracted information.

View as Markdown

#What it's for

With the end-of-call webhook you can:

  • save every call, with its outcome and summary, in your CRM or management software;
  • open a case or a ticket with the data the agent collected;
  • pass the call to Zapier or Make, and from there to hundreds of apps.
The call endsThe analysis prepares the outcome, summary and extracted data.
→
SupportFast sendsA POST request with the JSON, signed with your key.
→
Your server receivesIt checks the signature, responds and saves the data.

Before you start

  • The “Post-call webhook” feature in your plan.
  • An HTTPS address reachable from the Internet that accepts POST requests and responds within 10 seconds.

#Turning it on

  1. Open the Post-call step

    In the voice agent, go to Agent profileEdit ProfilePost-call: it's the fourth step of the setup. You can also get there from the Agent profile summary, with the button of the Post-call Actions area, which is called Edit, or Set up if the area isn't set up yet.

  2. Turn on the webhook

    In the Webhook panel, switch on Turn on the webhook and type the Webhook URL, which must start with https://.

  3. Choose the options

    Send only for completed calls, on by default, sends the webhook only for successful calls, explained further down. Include the audio recording URL, on by default, adds the audio link. If recording is off in the General step, the link stays empty.

  4. Save and copy the key

    Press Save changes: the key is created at that moment. In the Webhook Secret Key (HMAC Secret) panel, press Show the key or Copy the key and put it in your server's configuration.

With Regenerate the key you create a new one. The confirmation warns that “Signatures made with the current key will no longer be valid: update the system that receives the webhook.”. Reading and regenerating the key take effect immediately, without saving.

The voice agent's Post-call step: webhook on with the URL https://www.esempio.it/webhook/call-ended, the Send only for completed calls and Include the audio recording URL options on, and the hidden secret key with Show, Copy and Regenerate.
In the Post-call step, turn on the webhook, type the URL and copy the secret key.

#When it's sent

The webhook is sent after the end-of-call analysis: outcome, summary, sentiment and extracted variables are already in the JSON.

A call is successful when someone answered and said at least one word, or when it was transferred to a teammate. Voicemail, or an answer without words, doesn't count. It's the same definition as in Calls: list and details, and in the JSON it corresponds to call.reached equal to true.

  • With Send only for completed calls on, the webhook is sent only for successful calls, transferred ones included: despite the name, the “Completed” status doesn't matter.
  • With the option off, it's sent for every call that ended, even calls never answered or that went to voicemail. In those cases call.reached is false and call.endReason says why the call ended.

#What your server receives

A POST request with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json
X-Webhook-Signaturesha256= followed by the HMAC SHA-256 signature of the body, in hexadecimal.
X-Webhook-Call-IdThe call's identifier, the same as call.id.

JSON · example with test data

{
  "call": {
    "customerNumber": "+390212345678",
    "agentNumber": "+390612345678",
    "direction": "inbound",
    "startedAt": "19/10/2026 10:12",
    "endedAt": "19/10/2026 10:15",
    "duration": "3 min 5 s",
    "reached": true,
    "result": "appointment_booked",
    "resultText": "The customer booked a car service.",
    "sentiment": "positive",
    "summary": "Mario Rossi asks for a car service. The agent offers Thursday, October 22 at 9:30 and the customer accepts.",
    "transcript": "AI: Good morning, Officina Rossi, how can I help you?\n\nHUMAN: I'd like to book a car service.",
    "transferName": null,
    "recordingUrl": "https://www.esempio.it/registrazione-di-esempio.mp3",
    "appointment": {
      "startsAt": "22/10/2026 09:30",
      "calendarName": "Car service",
      "startsAtIso": "2026-10-22T07:30:00.000Z",
      "uid": "example-booking-123"
    },
    "variables": { "customer_name": "Mario Rossi", "license_plate": "AB123CD" },
    "id": "example-call-001",
    "conversationId": "example-conversation-001",
    "status": "ended",
    "endReason": "agent_endcall",
    "errorDetail": null,
    "durationSec": 185,
    "startedAtIso": "2026-10-19T08:12:04.000Z",
    "endedAtIso": "2026-10-19T08:15:09.000Z"
  },
  "messages": [
    { "role": "assistant", "content": "Good morning, Officina Rossi, how can I help you?" },
    { "role": "user", "content": "I'd like to book a car service." }
  ],
  "contact": {
    "fullName": "Mario Rossi",
    "email": "mario.rossi@esempio.it",
    "phone": "+390212345678"
  },
  "event": "telephone.ended",
  "variables": { "customer_name": "Mario Rossi", "license_plate": "AB123CD" }
}
event Text

Always telephone.ended.

call Object

The call data, with the same names as the {{call.*}} variables: what each field contains is in Call data. Readable fields are in the workspace time zone; those with Iso are dates in UTC. Coded fields have these values:

  • direction: inbound or outbound;
  • reached: true if the call was successful, as explained in When it's sent, otherwise false;
  • result: resolved, unresolved, abandoned, appointment_booked, transferred, no_interaction, no_answer, voicemail or spam;
  • sentiment: positive, neutral or negative;
  • status: ended, failed or transferred;
  • endReason, the reason the call ended: agent_endcall (ended by the agent), user_hangup (the customer hung up), timeout (maximum duration reached), silence_timeout (prolonged silence), transfer (transferred), voicemail (voicemail), no_answer (no answer), busy (busy), rejected (rejected), invalid_number (invalid number), blocked (blocked by the phone carrier), sip_error (line error) or error (technical error).

Duration is counted from the answer, without the ringing: for a call never answered, durationSec is 0 and duration is empty.

messages List

The conversation, message by message: role is assistant for the agent and user for the customer.

contact Object or null

The contact's full name, email and phone. It's null when no contact is linked to the call, for example if the caller's number is hidden.

variables Object

The variables extracted from the call, with the names you chose in Data Extraction and Variables. Empty if extraction is off. The same object is also in call.variables.

Missing data arrives as null or empty: for example the appointment, if none was booked, or the audio link, if nobody answered or the recording isn't ready yet.

#Verifying the signature

The signature tells you that the request really comes from SupportFast and hasn't been altered. Compute the HMAC SHA-256 of the body with the secret key, on the text exactly as you receive it and before parsing it into an object: even one different space changes the signature. Write it in hexadecimal with sha256= in front and compare it with the X-Webhook-Signature header.

Node.js with Express

const crypto = require("crypto");
const express = require("express");

const app = express();
const SEGRETO = process.env.SUPPORTFAST_WEBHOOK_SECRET;

app.post("/webhook/call-ended", express.raw({ type: "application/json" }), (req, res) => {
  const ricevuta = req.get("X-Webhook-Signature") || "";
  const attesa = "sha256=" + crypto.createHmac("sha256", SEGRETO).update(req.body).digest("hex");
  const valida = ricevuta.length === attesa.length &&
    crypto.timingSafeEqual(Buffer.from(ricevuta), Buffer.from(attesa));
  if (!valida) return res.sendStatus(401);

  res.sendStatus(200);
  const dati = JSON.parse(req.body.toString("utf8"));
  // save dati.call, dati.contact and dati.variables in your management software
});

app.listen(3000);

PHP

<?php
$segreto = getenv('SUPPORTFAST_WEBHOOK_SECRET');
$corpo = file_get_contents('php://input');
$ricevuta = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$attesa = 'sha256=' . hash_hmac('sha256', $corpo, $segreto);

if (!hash_equals($attesa, $ricevuta)) {
    http_response_code(401);
    exit;
}

http_response_code(200);
$dati = json_decode($corpo, true);
// save $dati['call'], $dati['contact'] and $dati['variables'] in your management software

#If your server doesn't respond

  • If the connection fails, if the 10 seconds pass or if you respond with a 5xx code, it retries after 1, 3 and 10 seconds. At most four deliveries in all.
  • With a 4xx code, it doesn't retry.
  • It doesn't follow redirects: a 3xx code counts as an error and isn't retried.
  • It doesn't send to internal or private addresses, only to servers reachable from the Internet.
  • After the last attempt, delivery stops. The call still stays on the agent's Calls page.

Respond right away, work afterwards. Respond 200 as soon as you've verified the signature and do the slow operations afterwards. To avoid saving the same call twice after a retry, check X-Webhook-Call-Id.

#How to test it

  1. Prepare the receiver

    Use your server or a test address that shows you incoming requests, such as those from Zapier or Make. Put it in Webhook URL and save.

  2. Make a real call

    Call the agent from your mobile phone, talk for a few seconds and hang up. With Send only for completed calls on, a call in which you say nothing doesn't trigger the webhook.

  3. Check what arrived

    The request arrives after the end-of-call analysis. Verify the signature and check the fields you need.

#With Zapier, Make or your management software

In Zapier, use the Catch Hook trigger of the Webhooks by Zapier app; in Make, the Custom webhook module. Copy the address they give you into Webhook URL: after the first call, the JSON fields are available in the following steps. These tools don't verify the signature on their own: keep the address private.

For your management software, ask the vendor whether it has a suitable address, or have a developer build one using the example above.

#Alternatively: an automation on telephony events

If you need only some of the data, or want to send it to several places, use an automation with an event from the Telephony category, such as Call ended. With the API node you call your server with the fields you choose; with the other nodes you send an email or a WhatsApp message.

End-of-call webhookAutomation
Where you set it upIn the voice agent's Post-call step, Webhook panelIn the workspace's automations
Data sentAlways all of it, in the same JSONThe data you choose
When it's sentAt the end of the call, transferred calls includedOn the chosen event: Call ended doesn't fire for transferred calls
SecurityHMAC signature with the keyThe headers you set in the API node
UsageNo executionsEach run counts toward the month's executions
Plan“Post-call webhook”Automations and, for the API node, “API and webhooks”

See Start events (triggers) and Calling an external API.

#Frequently asked questions

The signature never matches.

Check that you're using the current key and that the signature is in lowercase hexadecimal, with sha256= in front. The calculation is explained in Verifying the signature.

Can I send the data to more than one address?

The webhook has only one address per agent. For several destinations, go through Zapier or Make, or use an automation.

#See also