Integrations

Send contacts from your CRM

Instead of uploading a list, let your CRM, website form or automation tool push contacts into a workflow the moment they appear. Each contact you send is queued and called by the workflow's AI employee, inside its calling hours.

How it works

A workflow that starts from an API Trigger step has its own URL. Once you press Start calling on the workflow, it stays open and waits for contacts. Every POST to that URL adds one or more contacts to the queue. The workflow keeps waiting until you stop it.

Set it up

  1. Create an API key

    Create a key in the dashboard. See Create an API key. The key must have write access. A read-only key gets 403 This API key is read-only.

  2. Start the workflow from an API Trigger

    In the workflow builder, pick API Trigger as the contact source. Click the step to see the workflow's trigger URL and a ready-made curl example. The workflow ID is the part of the URL between /workflows/ and /trigger.

  3. Press Start calling

    Until the workflow is live, the endpoint answers 409. After you press Start calling, it accepts contacts until you stop the workflow.

  4. Send contacts

    Call the endpoint from your CRM, backend or automation tool, as in the examples below.

The endpoint

POST/workflows/{workflow_id}/trigger

https://calling.ubiqproject.com/api/v1/workflows/{workflow_id}/trigger · Auth: API key

Authenticate with X-API-Key: vk_… or Authorization: Bearer vk_…. Full details are in Authentication and the API reference.

Body (JSON) — send one of these

contactobject
One contact. Must include phone (or phone_number).
contactsobject[]
Up to 1,000 contacts in one request. You can send both contact and contacts; they are combined.

Contact fields

phonestringrequired
International format: + followed by 8–15 digits, e.g. +919876543210. A leading 00 is turned into +, and spaces and dashes are removed when the number starts with + or 00. Numbers without a country code are rejected. phone_number is accepted as an alternative key.
namestring
The contact's name. The AI employee can use it on the call.
any other keystring
Any extra field — loan_id, amount_due, city — is stored with the contact and can be used in the script as a {{placeholder}}, e.g. {{amount_due}}. Values are stored as text. Keys starting with _ are dropped.

One contact

curl
curl -X POST "https://calling.ubiqproject.com/api/v1/workflows/WORKFLOW_ID/trigger" \
  -H "X-API-Key: vk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact": {
      "name": "Rahul Sharma",
      "phone": "+919876543210",
      "loan_id": "LN-20417",
      "amount_due": "4,500"
    }
  }'

Several contacts

curl
curl -X POST "https://calling.ubiqproject.com/api/v1/workflows/WORKFLOW_ID/trigger" \
  -H "Authorization: Bearer vk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      { "name": "Rahul Sharma", "phone": "+919876543210", "city": "Pune" },
      { "name": "Priya Nair",   "phone": "00919812345678", "city": "Kochi" },
      { "name": "Amit Verma",   "phone": "98111 22233",    "city": "Delhi" }
    ]
  }'

Response

200 OK means the request was accepted. Check the body: some rows may still have been rejected or filtered out. In this example, the third number has no country code.

200 OK
{
  "queued": 2,
  "skipped_by_filter": 0,
  "invalid": [2],
  "detail": "Invalid rows need an international phone number, e.g. +919876543210."
}

Response fields

queuedinteger
Contacts added to the calling queue.
skipped_by_filterinteger
Valid contacts that don't match the workflow's call conditions (filters). They are stored but never called.
invalidinteger[]
Zero-based positions of rows whose phone number isn't international. Nothing is stored for them. In a request with contact and contacts, contact is position 0.
detailstring | null
A hint when some rows are invalid, otherwise null.

Node.js

trigger.mjs
// Node 18+ — e.g. inside your CRM's "lead created" hook
const res = await fetch(
  `https://calling.ubiqproject.com/api/v1/workflows/${process.env.UBIQ_WORKFLOW_ID}/trigger`,
  {
    method: "POST",
    headers: {
      "X-API-Key": process.env.UBIQ_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      contact: { name: "Rahul Sharma", phone: "+919876543210", source: "website-form" },
    }),
  },
);
const out = await res.json();
if (!res.ok) throw new Error(`Daffy Voice ${res.status}: ${out.detail}`);
console.log(`queued ${out.queued}, invalid rows: ${out.invalid.join(", ") || "none"}`);

Python

trigger.py
import os
import requests

r = requests.post(
    f"https://calling.ubiqproject.com/api/v1/workflows/{os.environ['UBIQ_WORKFLOW_ID']}/trigger",
    headers={"X-API-Key": os.environ["UBIQ_API_KEY"]},
    json={"contacts": [
        {"name": "Rahul Sharma", "phone": "+919876543210", "plan": "Gold"},
        {"name": "Priya Nair", "phone": "+919812345678", "plan": "Silver"},
    ]},
    timeout=30,
)
out = r.json()
if not r.ok:
    raise RuntimeError(f"Daffy Voice {r.status_code}: {out['detail']}")
print(out["queued"], "queued;", len(out["invalid"]), "invalid")

Errors

Errors return JSON with a detail message.

Status codes

401Unauthorized
Not authenticated (no key) or Invalid API key.
403Forbidden
This API key is read-only — use a key with write access.
404Not found
Workflow not found — wrong ID, or the workflow belongs to another workspace.
409Conflict
This workflow doesn't start from an API Trigger, or The workflow isn't calling — press Start calling on it first.
413Too large
At most 1000 contacts per request — split the list into batches.
422Unprocessable
The body has neither contact nor contacts, or isn't valid JSON.

Duplicates are skipped while waiting

If your CRM sends the same lead again while it's still waiting or on a call, it isn't queued twice. Once its call has finished, sending it again queues a new call — so send each lead on a "lead created" event, not on every update.

When calls happen

Queued contacts are called within the workflow's calling hours, at its pace, with its retry rules. A contact sent at night waits for the next calling window. See Calling hours, retries & callbacks.

Next steps