Testing webhooks
Using the dashboard
Every connection page has a Send Test Event button, which sends a synthetic event through the same code path as a real webhook — the request never actually leaves Sprio to hit LeadSquared or Zoho, so it verifies your assistant assignment, owner-gate, and dedup logic without depending on either provider being reachable. This works identically for both LeadSquared and Zoho CRM connections, and is always the right first step — before touching a script or a real CRM record — since it isolates "is Sprio's own pipeline configured correctly" from "is the provider actually delivering events."
Testing LeadSquared
Because LeadSquared posts lead fields flat on the request body, you can simulate a realistic event directly, in whatever tooling you're comfortable with:
- curl
- PowerShell
- Python
- Node.js
- Postman
curl -X POST "https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>" \
-H "X-Sprio-Webhook-Token: <your connection's secret token>" \
-H "Content-Type: application/json" \
-d '{"Lead": {"FirstName": "Test", "Phone": "+91XXXXXXXXXX"}}'
Invoke-RestMethod -Uri "https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>" `
-Method Post `
-Headers @{ "X-Sprio-Webhook-Token" = "<your connection's secret token>" } `
-ContentType "application/json" `
-Body '{"Lead": {"FirstName": "Test", "Phone": "+91XXXXXXXXXX"}}'
import requests
response = requests.post(
"https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>",
headers={"X-Sprio-Webhook-Token": "<your connection's secret token>"},
json={"Lead": {"FirstName": "Test", "Phone": "+91XXXXXXXXXX"}},
)
print(response.status_code, response.json())
const response = await fetch(
"https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>",
{
method: "POST",
headers: {
"X-Sprio-Webhook-Token": "<your connection's secret token>",
"Content-Type": "application/json",
},
body: JSON.stringify({ Lead: { FirstName: "Test", Phone: "+91XXXXXXXXXX" } }),
}
);
console.log(response.status, await response.json());
- New request →
POST→https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId> - Headers tab: add
X-Sprio-Webhook-Token=<your connection's secret token> - Body tab → raw → JSON:
{"Lead": {"FirstName": "Test", "Phone": "+91XXXXXXXXXX"}}
- Send — expect
200 {"received": true}
| Response | Meaning |
|---|---|
200 {"received": true} | Token accepted, event queued for processing |
401 {"error": "unauthorized"} | Token header missing or doesn't match the connection's stored token — check the value on your connection's dashboard page |
404 {"error": "unknown connection"} | The connectionId in the URL doesn't exist |
A 200 here only confirms the request was accepted — it doesn't mean a
call was placed. Check Integrations → Activity to see the actual
downstream outcome (owner-gate skip, dedup skip, or a real call queued).
Testing the owner-reassignment shape specifically
If you want to test owner-reassignment handling (not just lead-creation)
directly, remember the payload must be wrapped, and your test owner ID
must match trigger_owner_user_id on the connection:
- curl
- PowerShell
- Python
- Node.js
curl -X POST "https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>" \
-H "X-Sprio-Webhook-Token: <your connection's secret token>" \
-H "Content-Type: application/json" \
-d '{
"Before": {"OwnerId": "old-owner-guid", "ProspectID": "test-123"},
"After": {"OwnerId": "your-trigger-owner-guid", "ProspectID": "test-123", "FirstName": "Test", "Phone": "+91XXXXXXXXXX"}
}'
$body = @{
Before = @{ OwnerId = "old-owner-guid"; ProspectID = "test-123" }
After = @{ OwnerId = "your-trigger-owner-guid"; ProspectID = "test-123"; FirstName = "Test"; Phone = "+91XXXXXXXXXX" }
} | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>" `
-Method Post `
-Headers @{ "X-Sprio-Webhook-Token" = "<your connection's secret token>" } `
-ContentType "application/json" `
-Body $body
import requests
response = requests.post(
"https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>",
headers={"X-Sprio-Webhook-Token": "<your connection's secret token>"},
json={
"Before": {"OwnerId": "old-owner-guid", "ProspectID": "test-123"},
"After": {
"OwnerId": "your-trigger-owner-guid",
"ProspectID": "test-123",
"FirstName": "Test",
"Phone": "+91XXXXXXXXXX",
},
},
)
print(response.status_code, response.json())
const response = await fetch(
"https://api.voice-up.sprio.ai/api/webhooks/leadsquared/<connectionId>",
{
method: "POST",
headers: {
"X-Sprio-Webhook-Token": "<your connection's secret token>",
"Content-Type": "application/json",
},
body: JSON.stringify({
Before: { OwnerId: "old-owner-guid", ProspectID: "test-123" },
After: {
OwnerId: "your-trigger-owner-guid",
ProspectID: "test-123",
FirstName: "Test",
Phone: "+91XXXXXXXXXX",
},
}),
}
);
console.log(response.status, await response.json());
Sending the fields flat (unwrapped) here will silently fail owner
matching, since Sprio only reads payload.After for this event shape —
if a test like this produces no call and no obvious error, this is the
first thing to check.
Testing Zoho CRM
Zoho's real webhook payload carries only a record ID and a token — there's no equivalent flat request example that reflects reality, since Sprio always fetches the full lead from Zoho's own API rather than trusting the notification body. This makes direct request testing much less useful for Zoho than for LeadSquared. Instead:
- Start with Send Test Event on the connection page — this exercises the entire processing pipeline (owner-gate, dedup, assistant assignment) without touching Zoho at all, and is the fastest way to confirm everything downstream of "a notification arrived" is configured correctly.
- To test the real end-to-end path, create or reassign a lead in your actual Zoho CRM account and watch the connection's Activity tab. Zoho's notification delivery timing isn't instant or tightly bounded — give it a couple of minutes before concluding it didn't fire.
- If nothing arrives after a real change, check the connection's watch channel status before assuming the integration is broken — Zoho has, in practice, reported a channel as healthy while silently delivering nothing (see Integration: Zoho CRM). A dashboard's own "channel active" indicator isn't sufficient proof by itself; if this is suspected, ask Sprio support to check the independent health-check result for that channel.
If you do want to simulate a raw Zoho notification for pipeline testing (understanding that this bypasses Zoho's own API entirely and Sprio will still try to fetch the referenced lead ID for real):
- curl
- PowerShell
- Python
curl -X POST "https://api.voice-up.sprio.ai/api/webhooks/zoho-crm/<connectionId>" \
-H "Content-Type: application/json" \
-d '{"token": "<your connection'"'"'s token>", "module": "Leads", "operation": "update", "ids": ["4876876000001234567"]}'
$body = @{
token = "<your connection's token>"
module = "Leads"
operation = "update"
ids = @("4876876000001234567")
} | ConvertTo-Json
Invoke-RestMethod -Uri "https://api.voice-up.sprio.ai/api/webhooks/zoho-crm/<connectionId>" `
-Method Post `
-ContentType "application/json" `
-Body $body
import requests
response = requests.post(
"https://api.voice-up.sprio.ai/api/webhooks/zoho-crm/<connectionId>",
json={
"token": "<your connection's token>",
"module": "Leads",
"operation": "update",
"ids": ["4876876000001234567"],
},
)
print(response.status_code, response.json())
Use a real lead ID from your Zoho account for ids — Sprio will actually
call Zoho's API to fetch it, so a made-up ID will fail lookup rather than
producing a useful test.
Checking activity
Every webhook call (test or real) appears in Integrations → Activity for that connection, along with whether it triggered a call and why. Common outcomes you'll see there, and what they mean:
| Activity outcome | Meaning |
|---|---|
call queued | A real call was placed |
owner_mismatch | Received and valid, but the lead's owner didn't match trigger_owner_user_id — expected behavior, not an error |
phone_already_called | Cross-assistant dedup blocked a repeat dial to this number |
already_called / already_called_race | This exact lead already triggered a call earlier today |
auto_call_disabled | The connection received the event but auto-calling is toggled off |
no_dialable_phone | No usable phone number was found in the payload or the fetched record |
no_lead_id_in_payload | Sprio couldn't find a lead identifier field at all — often means a source-side field naming change; the raw payload keys are logged alongside this for diagnosis |
This is the authoritative place to check outcomes — a webhook can be
received and acknowledged (200) but still be legitimately skipped
downstream, which won't show up as an error in the source CRM's own
delivery logs. If a client reports "nothing happened," start here before
assuming a bug.