Inbound Webhooks Reference
Inbound webhooks allow your systems to trigger actions in HuskyVoice AI (like starting a call) by sending authorized HTTP POST requests to a unique endpoint.
| Inbound Webhook | REST API | |
|---|---|---|
| Best for | No-code tools, CRMs, Zapier, HubSpot | Custom applications, backend integrations |
| Auth | Secret Token in URL | API Key in header |
Endpoint Configuration
Every inbound webhook is assigned a unique Secret Token that identifies your organization.
- Method:
POST - URL Format:
https://api.huskyvoice.ai/v1/hooks/{YOUR_SECRET_TOKEN} - Content-Type:
application/json
Making a Call via Inbound Webhook
Follow these steps to trigger a call using your Inbound Webhook:
- Get your Secret Token — Find it in Dashboard → Integrations → Inbound Webhooks
- Set the action — Use
call.createin the request body - Add contact details — Include
agent_idandcontact_numberat minimum - Send the request — POST to
https://api.huskyvoice.ai/v1/hooks/YOUR_SECRET_TOKEN - Check the response — A
call_idwithstatus: queuedconfirms the call is initiated
- cURL
- Python
- JavaScript
- n8n
curl -X POST https://api.huskyvoice.ai/v1/hooks/YOUR_SECRET_TOKEN \
-H "Content-Type: application/json" \
-d '{
"action": "call.create",
"data": {
"agent_id": "agent_abc_123",
"contact_number": "+919876543210",
"contact_name": "John Doe"
}
}'
import requests
url = "https://api.huskyvoice.ai/v1/hooks/YOUR_SECRET_TOKEN"
payload = {
"action": "call.create",
"data": {
"agent_id": "agent_abc_123",
"contact_number": "+919876543210",
"contact_name": "John Doe"
}
}
response = requests.post(url, json=payload)
print(response.json())
const response = await fetch(
"https://api.huskyvoice.ai/v1/hooks/YOUR_SECRET_TOKEN",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "call.create",
data: {
agent_id: "agent_abc_123",
contact_number: "+919876543210",
contact_name: "John Doe"
}
})
}
);
const data = await response.json();
console.log(data);
Authentication uses the secret token embedded in the URL. No separate credential setup is required in n8n.
{
"name": "HuskyVoice – Trigger Call via Inbound Webhook",
"nodes": [
{
"parameters": {
"method": "POST",
"url": "https://api.huskyvoice.ai/v1/hooks/YOUR_SECRET_TOKEN",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\n \"action\": \"call.create\",\n \"data\": {\n \"agent_id\": \"agent_abc_123\",\n \"contact_number\": \"+919876543210\",\n \"contact_name\": \"John Doe\"\n }\n}"
},
"id": "1",
"name": "Trigger HuskyVoice Call",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [250, 300]
}
],
"connections": {},
"settings": {},
"meta": { "instanceId": "huskyvoice-docs" }
}
Sample Response:
{
"success": true,
"call_id": "call_abc_123",
"status": "queued",
"message": "Call has been queued successfully."
}
Replace YOUR_SECRET_TOKEN with the token found in your HuskyVoice dashboard under Integrations → Inbound Webhooks.
Supported Actions
The request body must include an action and a corresponding data object.
1. call.create
Triggers an outbound call to a specific contact using a defined AI Agent.
Payload Schema:
{
"action": "call.create",
"data": {
"agent_id": "agent_abc_123",
"contact_number": "+919876543210",
"contact_name": "John Doe",
"contact_email": "john@example.com",
"additional_info": {
"order_id": "ORD_999",
"priority": "high"
}
}
}
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | Yes | The ID of the agent to use for the call. |
contact_number | string | Yes | Phone number with country code (e.g., +91XXXXXXXXXX). |
contact_name | string | No | Name of the recipient for AI context. |
contact_email | string | No | Email address for follow-up notifications. |
additional_info | object | No | Key-value pairs (max 25) for custom agent metadata. |
2. call.cancel
Attempts to cancel a call that is still in a queued state.
Payload Schema:
{
"action": "call.cancel",
"data": {
"call_id": "call_xyz_789"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
data.call_id | string | Yes | The call_id of the queued call to cancel. |
3. call.delete
Permanently deletes a completed call and erases all associated end-customer PII (transcript, contact details, recording metadata). Only works on calls that have a recorded engagement (i.e., calls that were connected). See Deleting Calls for full details.
Payload Schema:
{
"action": "call.delete",
"data": {
"call_id": "call_xyz_789"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
data.call_id | string | Yes | The call_id of the completed call to delete |
Security & Verification (Optional)
If you enable Signature Verification for your webhook, HuskyVoice will validate the sender using HMAC-SHA256 signatures.
Headers
| Header | Description |
|---|---|
X-Inbound-Timestamp | Current Unix timestamp. |
X-Inbound-Signature | The HMAC-SHA256 signature string. |
Signature Validation
Every incoming request from HuskyVoice includes a signature in the X-Inbound-Signature header. You must validate this on your server to confirm the request is genuine and has not been tampered with.
How to validate:
- Read the
X-Inbound-TimestampandX-Inbound-Signatureheaders from the incoming request - Recreate the signature by hashing
timestamp.rawBodyusing your Inbound Signing Secret - Compare your generated signature with the one in the header
- Reject the request if they do not match — return
401 Unauthorized
- cURL
- Python
- JavaScript
- n8n
# Compute the expected signature to compare with X-Inbound-Signature
SECRET="YOUR_INBOUND_SIGNING_SECRET"
TIMESTAMP="1748700000"
PAYLOAD='{"action":"call.create","data":{"agent_id":"agent_abc_123"}}'
printf '%s.%s' "$TIMESTAMP" "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET"
# Compare the hex output with the X-Inbound-Signature header value
import hmac
import hashlib
def validate_signature(timestamp: str, raw_body: str, received_sig: str, signing_secret: str) -> bool:
message = f"{timestamp}.{raw_body}"
expected = hmac.new(
signing_secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(received_sig, expected)
const crypto = require('crypto');
function validateSignature(req, signingSecret) {
const timestamp = req.headers['x-inbound-timestamp'];
const receivedSignature = req.headers['x-inbound-signature'];
const rawBody = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha256', signingSecret);
hmac.update(`${timestamp}.${rawBody}`);
const expectedSignature = hmac.digest('hex');
if (receivedSignature !== expectedSignature) {
return false; // Reject — signature mismatch
}
return true; // Valid request
}
// n8n Code node — validate inbound webhook signature
const crypto = require("crypto");
const signingSecret = "YOUR_INBOUND_SIGNING_SECRET";
const timestamp = $input.first().json.headers["x-inbound-timestamp"];
const receivedSig = $input.first().json.headers["x-inbound-signature"];
const rawBody = JSON.stringify($input.first().json.body);
const message = `${timestamp}.${rawBody}`;
const expectedSig = crypto
.createHmac("sha256", signingSecret)
.update(message)
.digest("hex");
if (receivedSig !== expectedSig) {
throw new Error("Invalid signature — request rejected");
}
return $input.all();
Always reject requests that fail signature validation. Never process webhook payloads from unverified sources.
Key Rotation
Rotating your Secret Token invalidates the old token immediately. Any requests using the old token will receive a 401 Unauthorized response.
When to rotate:
- If your Secret Token is accidentally exposed or leaked
- As part of a regular security audit cycle
How to rotate:
- Go to Dashboard → Integrations → Inbound Webhooks
- Click Regenerate Token
- Update your external systems (Zapier, HubSpot, etc.) with the new token immediately
Update all connected systems with the new token before rotating — existing integrations using the old token will stop working immediately after rotation.
Response Codes
200 OK:call.cancelorcall.deleteaction completed successfully.201 Created:call.createaction successfully queued.400 Bad Request: Validation error (e.g., invalid phone format).401 Unauthorized: Invalid token or signature failure.402 Payment Required: Insufficient credits to trigger the action.404 Not Found:call.cancelorcall.delete— no matching call found.409 Conflict:call.cancel— call exists but is not in a cancellable state.429 Too Many Requests: Daily request quota exceeded.