Skip to content
Website

Webhook triggers: payload and HMAC signing ​

A webhook trigger lets your system tell an agent "act on this contact now" — for example, call a guest when a booking is confirmed, or WhatsApp a customer when an order ships. You send one signed HTTP request; the agent does the rest.

Set up the trigger first: Sidebar → Agents → open an agent → Triggers → add When an external system notifies us. Step-by-step: Webhook triggers and signing secrets.

The route ​

text
POST https://api.voxido.ai/api/v1/agents-v4/{agent_id}/webhook

The URL belongs to the agent. If the agent has two webhook triggers, both share it, and either trigger's secret is accepted.

Sign the request ​

HeaderValue
Content-Typeapplication/json
X-Voxido-SignatureLowercase hex HMAC-SHA256 of the raw request body, using the trigger's signing secret as the key

Rules:

  • Don't send the secret itself. Send the HMAC of the body.
  • No timestamp, no sha256= prefix.
  • Sign the exact bytes you send. Re-formatting the JSON after signing breaks the signature.
  • Use the trigger's signing secret — not a Voxido API key. API keys aren't accepted on this route.

Python ​

python
import hashlib, hmac, json, requests

secret = "THE_TRIGGER_SIGNING_SECRET"
body = json.dumps({"phone": "+919812345678", "context": {"order_id": "BW-1042"}})
signature = hmac.new(secret.encode(), body.encode(), hashlib.sha256).hexdigest()

requests.post(
    "https://api.voxido.ai/api/v1/agents-v4/AGENT_ID/webhook",
    data=body,
    headers={"Content-Type": "application/json", "X-Voxido-Signature": signature},
)

Node.js ​

js
import crypto from "node:crypto";

const body = JSON.stringify({ phone: "+919812345678", context: { order_id: "BW-1042" } });
const signature = crypto.createHmac("sha256", process.env.TRIGGER_SECRET).update(body).digest("hex");

await fetch(`https://api.voxido.ai/api/v1/agents-v4/${agentId}/webhook`, {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-Voxido-Signature": signature },
  body,
});

The payload ​

json
{
  "contact_id": "c_01H...",
  "phone": "+919812345678",
  "trigger_source": "booking_confirmed",
  "context": {
    "reservation_id": "R-12345",
    "check_in_date": "2026-05-18"
  }
}
FieldNeeded?How it's used
contact_id (or contactId)One way to name the contactUsed first, if present
phone (or phone_number, to) or emailIf there's no contact_idFinds the contact in the agent's workspace, or creates one
trigger_sourceNoA label for you. Passed to the agent with the rest.
contextNoAny object. Passed to the agent with the rest.

The whole body reaches the agent as event data, so it can use any field you send. If the body has no contact_id, phone or email, the request is accepted but nothing runs.

Responses ​

StatusBodyMeaning
200{"status": "accepted"}Signature is valid; the event is queued
401"Invalid signature"Missing or wrong X-Voxido-Signature
404"No webhook trigger on this agent"The agent has no webhook trigger, or doesn't exist
404"Agent not found"The agent was deleted
429"Rate limit exceeded. Please try again later."More than 30 requests a minute

200 doesn't mean the agent ran

The work happens after the reply. If the agent isn't Active, the event is dropped quietly. Check the agent's status, then watch Sidebar → Agent activity.

Retries and duplicates ​

Voxido treats every request as new. If your tool retries a request, the agent runs again. Retry only on 429 or network errors, not on 200.

Turning a sender off ​

To cut a sender off for good, Regenerate the trigger's signing secret or remove the trigger. Requests signed with the old secret then get 401. Switching the trigger off may not stop a sender that still holds a valid secret, so regenerate the secret as well.