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

  1. Open Integrations in Sayify.
  2. Choose Custom Webhook.
  3. Enter your endpoint URL, for example https://api.example.com/sayify/webhook.
  4. Choose subscribed events:
    • response.started
    • response.submitted
  5. Optionally configure a signing secret.
  6. Save the integration.
  7. 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-2xx HTTP 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.

Was this page helpful?
Report an issue →