Webhooks
Ingest custom events with a source webhook key, or evaluate routing rules synchronously.
Custom webhook endpoint
POST https://api.warpflow.ai/api/v1/events/{tenant_id}/{source_id}
Authorization: Bearer <source_webhook_key>
Content-Type: application/jsonAsk your operator to provision a webhook key for source_id. This endpoint validates the source key configured in WEBHOOK_API_KEYS; a tenant API key created in the dashboard is not accepted. Use the tenant and source assigned to your integration.
Payload
{
"entity": {
"id": "crm-12345",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+15551234567"
},
"content": {
"body": "I need a quote for a kitchen remodel.",
"subject": "Quote request",
"channel": "webhook",
"metadata": { "form_id": "abc123" }
},
"context": { "campaign": "spring" },
"timestamp": "2026-02-22T14:30:00Z"
}entity supplies identity; its id is stored as an external ID. content.body supplies message text, with event_type as a fallback. content.channel defaults to webhook; content.direction defaults to inbound. Optional entity.company, entity.metadata, content.type, content.metadata, and context carry additional context. The URL supplies the source. Top-level contact, channel, source, and metadata are not consumed by this endpoint.
Default asynchronous ingestion
The default mode persists the inbound lead/conversation and emits a signal event for background handling when those stores are configured. A successful response is 200:
{ "event_id": "webhook_typeform_123_abc", "status": "accepted" }If the state store is unavailable, the endpoint instead returns 200 with status: "skipped" and reason: "state_store_unavailable". Acceptance is not proof that any downstream action completed.
Synchronous rules evaluation
Add ?mode=sync to evaluate routing rules immediately. This mode evaluates rules only: it does not run AI classification, scoring, reply generation, or execute the returned actions. A successful response is 200:
{
"event_id": "webhook_typeform_123_abc",
"status": "processed",
"classification": null,
"matched_rules": 1,
"actions_triggered": ["escalate"]
}Use this mode to inspect routing matches. For the full synchronous AI pipeline with a tenant API key, use the Zapier pipeline, whose request and response differ.
Errors
An invalid source webhook key returns 401 UNAUTHORIZED; missing webhook-key configuration returns 503 SERVICE_UNAVAILABLE; an unknown tenant in sync mode returns 404 NOT_FOUND. Malformed payloads can return a server error. This endpoint does not promise a per-tenant throttle or a Retry-After header.
Outbound webhook subscriptions
Register a subscription through /api/v1/tenants/{tenant_id}/webhooks and use its test endpoint to send a signed test event to your HTTPS receiver. Automatic application event fan-out is not currently wired; creating a subscription does not guarantee automatic deliveries. Event types include lead.received, lead.scored, lead.tier_changed, lead.state_changed, lead.contact_replied, contact.created, contact.updated, and conversation.updated. An empty event filter matches all event types.
Deliveries are HTTP POST requests with this envelope:
{
"event_id": "evt_123_abc",
"event_type": "lead.scored",
"tenant_id": "acme",
"timestamp": 1771784200000,
"data": {}
}The payload timestamp is Unix epoch milliseconds. data varies by event type. Headers include X-Webhook-Event, X-Webhook-Delivery, X-Webhook-Timestamp, and X-Webhook-Signature. The header timestamp is Unix epoch seconds. Verify the signature as the hexadecimal HMAC-SHA256 digest, using the subscription secret, of X-Webhook-Timestamp + "." + rawRequestBody. Verify the original body bytes before parsing or reserializing JSON, compare signatures with a constant-time comparison, and reject stale timestamps according to your receiver's replay policy.
The exported direct fan-out helper, when invoked, makes one delivery attempt per matching active subscription. The application currently invokes delivery from the subscription test endpoint; it does not invoke this automatic fan-out helper. The fan-out helper treats any 2xx response as success and tracks consecutive failures; a successful delivery through that helper resets the count. Test delivery does not update these counters. When the helper records 10 consecutive failures, the subscription store changes status to paused. Subscription statuses are active, paused, and disabled. This direct delivery path does not provide automatic retries or durable queue guarantees. Make receivers safe to handle duplicate events and monitor failures rather than assuming failed deliveries will be retried.