Configure custom webhook endpoints, verify Sayify signatures, inspect payloads, and troubleshoot delivery retries.
Webhook Integration
Custom webhooks send nested JSON payloads from Sayify to your own HTTP endpoint. Use them for custom CRMs, data warehouses, internal automation, server-side workflows, or any system that can receive HTTPS POST requests.
For most custom integrations, use a Custom Webhook destination from the Integrations page. It includes event subscriptions, delivery logs, retry state, testing, and form-level assignment.
Webhook Types
| Type | Where Configured | Best For | Payload |
|---|---|---|---|
| Custom Webhook integration | Integrations page | Production integrations with logs and retries | Nested response event JSON |
| Form-level webhook assignment | Form Builder > Automation > Always-On Integrations | Sending one form to a specific endpoint | Nested response event JSON |
| Notification Settings webhook | Workspace Global Settings | Alert notifications or simple global form-submit URL | Alert payload or response.submitted payload |
Use Custom Webhook integrations when you need reliable delivery, retries, and history.
Requirements
| Requirement | Details |
|---|---|
| Endpoint URL | Use HTTPS in production. Localhost only works for local development. |
| HTTP method | Sayify sends POST. |
| Content type | application/json. |
| Response code | Return any 2xx status for success. |
| Timeout | Respond quickly. Long processing should be queued on your side. |
| Idempotency | Use X-Sayify-Delivery-Id to deduplicate retries. |
Configure a Custom Webhook
- Open Integrations in Sayify.
- Choose Custom Webhook.
- Enter your endpoint URL, for example
https://api.example.com/sayify/webhook. - Choose subscribed events:
response.startedresponse.submitted
- Optionally configure a signing secret.
- Save the integration.
- Use Test to verify your endpoint receives requests.
To make it form-specific, open Form Builder > Automation > Always-On Integrations and assign the webhook to the form.
Events
| Event | Timing | Includes AI Data |
|---|---|---|
response.started |
When a respondent starts a session | No |
response.submitted |
After submission and AI case enrichment | Yes |
destination_item.approved |
When reviewed destination data is approved and pushed | Approved item payload |
Use response.submitted when you need answers, contact details, custom fields, use-case metrics, or AI summaries.
Headers
Custom Webhook integrations include:
| Header | Description |
|---|---|
Content-Type |
application/json |
X-Sayify-Event |
Event name, such as response.submitted. |
X-Sayify-Delivery-Id |
Unique delivery UUID for idempotency. |
X-Sayify-Signature |
Optional HMAC-SHA256 signature if a secret is configured. Format: sha256=<hex>. |
User-Agent |
Sayify-Webhooks/1.0 |
response.started Payload
{
"event": "response.started",
"timestamp": "2026-02-17T10:13:15.123Z",
"session": {
"uuid": "abc123-session-uuid-456",
"link_name": "Lead Qualification",
"link_slug": "lead-qualification",
"link_uuid": "def789-link-uuid-012",
"started_at": "2026-02-17T10:13:15.123Z"
},
"respondent": {
"ip_address": "192.0.2.1",
"user_agent": "Mozilla/5.0",
"location": {
"country": "US",
"city": "San Francisco"
}
},
"workspace": {
"uuid": "workspace-uuid-123",
"name": "Acme Corp"
},
"use_case": "lead_qualification"
}
response.submitted Payload
{
"event": "response.submitted",
"timestamp": "2026-02-17T10:13:30.794Z",
"contact": {
"name": "Alex Johnson",
"email": "alex@example.com",
"phone": "+14155551234",
"status": "respondent",
"company": "Acme Corp"
},
"session": {
"uuid": "abc123-session-uuid-456",
"link_name": "Lead Qualification",
"link_slug": "lead-qualification",
"link_uuid": "def789-link-uuid-012",
"started_at": "2026-02-17T10:13:15.123Z",
"completed_at": "2026-02-17T10:13:30.794Z",
"duration_seconds": 85.79
},
"metadata": {
"total_questions": 4,
"answered_questions": 4,
"average_response_time": "21s",
"completion_percentage": 100
},
"responses": {
"company_size": {
"value": "51-200",
"question": "How large is your company?",
"response_type": "dropdown"
},
"current_solution": {
"value": "We use spreadsheets today.",
"question": "What are you using today?",
"response_type": "voice_text_fallback",
"ai_evaluation": {
"clarity_score": 82,
"completion_status": "complete",
"insights": {
"sentiment_score": 6,
"urgency_level": "medium",
"executive_summary": "The respondent is using spreadsheets and appears open to change."
}
}
}
},
"workspace": {
"name": "Acme Corp",
"uuid": "workspace-uuid-123"
},
"respondent": {
"ip_address": "192.0.2.1",
"user_agent": "Mozilla/5.0",
"location": {
"city": "San Francisco",
"country": "US"
}
},
"use_case": "lead_qualification",
"extracted_data": {
"company_size": "Mid-Market",
"decision_process": "Finance approval required"
},
"custom_fields": {
"decision_process": "Finance approval required"
},
"use_case_metrics": {
"lead_score": 88,
"icp_fit": "Strong Fit"
},
"use_case_key_points": {
"buying_signals": [
"Asked about implementation timeline"
]
},
"extracted_fields": {
"Field:decision_process": "Finance approval required"
},
"ai_metrics": {
"Metric:lead_score": 88
}
}
Payload Blocks
| Block | Description |
|---|---|
contact |
Respondent identity captured from contact fields and CRM matching. |
session |
Session and form metadata. |
metadata |
Question counts, completion percentage, and response timing. |
responses |
Per-question answer data keyed by form field key. |
workspace |
Workspace identity. |
respondent |
IP, user agent, and location metadata. |
use_case |
Form use-case key. |
extracted_data |
AI-extracted case fields. |
custom_fields |
Form-specific custom extraction fields. |
use_case_metrics |
Dynamic metrics produced for the form use case. |
use_case_key_points |
Dynamic arrays of highlights, risks, themes, or other key points. |
extracted_fields |
Legacy Field:<key> aliases. |
ai_metrics |
Legacy Metric:<key> aliases. |
Verify Signatures
If you configure a webhook secret, Sayify signs the JSON payload with HMAC-SHA256.
Example verification in Node.js:
import crypto from "crypto";
function verifySayifySignature(rawBody, signatureHeader, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signatureHeader || ""),
Buffer.from(expected)
);
}
Use the raw request body for verification. Do not reserialize parsed JSON before computing the signature.
Retry Policy
Custom Webhook integrations retry failed deliveries with exponential backoff. A delivery is considered successful when your endpoint returns a 2xx status code.
Retries are triggered for:
- Network errors.
- Timeouts.
- Non-
2xxHTTP responses. - Temporary endpoint failures.
Use X-Sayify-Delivery-Id as an idempotency key so your endpoint can safely handle retries.
Local Testing
For local development, use a tunnel such as ngrok or Cloudflare Tunnel so Sayify can reach your machine.
You can also use the built-in test receiver in development:
http://localhost:9000/api/test-webhook/
That endpoint prints the payload it receives, which is useful while checking custom fields, use-case metrics, and AI enrichment.
Example Endpoint
Express example:
import express from "express";
const app = express();
app.use(express.json());
app.post("/sayify/webhook", (req, res) => {
const event = req.header("X-Sayify-Event");
const deliveryId = req.header("X-Sayify-Delivery-Id");
console.log("Sayify event:", event);
console.log("Delivery:", deliveryId);
console.log("Payload:", req.body);
res.status(200).json({ ok: true });
});
app.listen(3000);
Troubleshooting
| Problem | What To Check |
|---|---|
| No request received | Confirm endpoint URL is public, integration is enabled, and event subscription matches. |
| Request times out | Return 2xx quickly and process work asynchronously. |
| Signature mismatch | Verify against the raw request body and correct secret. |
| Duplicate processing | Deduplicate using X-Sayify-Delivery-Id. |
| AI/custom fields empty | Use response.submitted and wait for AI enrichment. |
| Delivery skipped | Check plan gates, filters, form assignment, and integration enabled state. |