Integrations

Send results to your tools

Add a Send results step to a workflow and Daffy Voice posts the result of every call — outcome, notes, promised dates and the details your AI employee captured — to your own server or straight into a Google Sheet.

What gets sent

After each call in a workflow, Daffy Voice sends one JSON POST with the event call.result. Answered calls are sent once the transcript is ready and the call has been summarised and classified, so expect a short delay after hang-up. Calls that didn't connect (no answer, busy, voicemail, failed) are sent straight away.

Each payload has two views of the same result: structured fields (contact, result) for code, and a flat row of column → value for spreadsheets and "add row" automations. The full schema is in Events & payloads.

Add a Send results step

  1. Open the workflow and add Send results

    In the workflow builder, add a Send results action and click it to open its settings.

  2. Choose where to send

    Send to: Webhook for your own server, Zapier, Make, n8n or a CRM that accepts webhooks; or Google Sheet to add one row per call to a spreadsheet (see below).

  3. Choose which calls

    • Every call — a result for every contact the workflow calls, whatever happened.
    • Only connected branches — only when the call's outcome matches an outcome branch that is wired to this step. An unwired step sends nothing.
  4. Add a signing secret (recommended)

    For webhooks, enter any secret text in Signing secret. Each request then carries an X-Ubiq-Signature header you can check, so you know it came from Daffy Voice. See Verifying signatures.

  5. Send a test

    Press Send a test. Daffy Voice posts a sample result (with "test": true added) to your URL and tells you whether it was accepted. Use it to map fields in your tool before real calls start.

Allowed URLs

The URL must be http:// or https:// and must point to a public address. Private, loopback and other internal network addresses (for example localhost, 10.x.x.x, 192.168.x.x) are refused. To test locally, expose your server with a tunnel and use its public HTTPS URL.

Send to a Google Sheet

The sheet receives the flat row from each result. A small Apps Script inside your sheet accepts the request, adds any column it hasn't seen before to the header row of the first tab, and appends the result there.

  1. Open Apps Script

    In your Google Sheet, open Extensions → Apps Script.

  2. Paste the script

    Replace the contents of the editor with this script and save. The same script is shown in the step's settings.

    Code.gs
    function doPost(e) {
      var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
      var row = JSON.parse(e.postData.contents).row || {};
      var lastCol = sheet.getLastColumn();
      var headers = lastCol ? sheet.getRange(1, 1, 1, lastCol).getValues()[0] : [];
      Object.keys(row).forEach(function (k) {
        if (headers.indexOf(k) < 0) { headers.push(k); sheet.getRange(1, headers.length).setValue(k); }
      });
      sheet.appendRow(headers.map(function (h) { return row[h] === undefined ? "" : row[h]; }));
      return ContentService.createTextOutput("ok");
    }
  3. Deploy it as a web app

    Deploy → New deployment, choose Web app, set Who has access to Anyone, and deploy. Approve the permissions Google asks for.

  4. Paste the Web app URL

    Copy the Web app URL (it ends in /exec) into the Send results step, then press Send a test. A row should appear in the sheet.

Anyone with the URL can add rows

A web app open to "Anyone" accepts posts from anyone who has the URL, and this script doesn't check signatures. Keep the URL private. If you need verified data, send to your own webhook instead.

Receive results on your server

Return any 2xx status within 10 seconds to confirm delivery. Do slow work (CRM updates, emails) after you respond. Failed deliveries are retried twice — see Errors & retries.

Node.js (Express)

server.mjs
import express from "express";

const app = express();

app.post("/ubiq/results", express.json(), (req, res) => {
  const { event, contact, result } = req.body;
  if (event !== "call.result") return res.sendStatus(204);

  // e.g. update the lead in your CRM
  console.log(contact.phone, result.answered ? result.outcome : result.call_status);

  res.sendStatus(200); // answer quickly — anything other than 2xx counts as a failure
});

app.listen(3000);

Python (Flask)

app.py
from flask import Flask, request

app = Flask(__name__)

@app.post("/ubiq/results")
def ubiq_results():
    event = request.get_json()
    if event.get("event") != "call.result":
        return "", 204
    contact, result = event["contact"], event["result"]
    print(contact["phone"], result["outcome"] if result["answered"] else result["call_status"])
    return "", 200

These minimal examples don't check the signature. In production, verify X-Ubiq-Signature against the raw body before trusting the data — the security page has complete Node and Python code.

Useful fields

Most-used fields

result.answeredboolean
True when the call connected and has a duration.
result.outcomestring | null
The outcome label from your workflow, e.g. Promised to pay. null if the call wasn't classified.
result.call_statusstring | null
How the dial ended, e.g. no_answer, busy, voicemail.
result.detailsobject
The details your AI employee captured, keyed by their labels.
contactobject
Every field you uploaded or sent for this contact, plus name and phone.
rowobject
The same result as one flat row, ready for a spreadsheet.

Next steps