Skip to main content

Webhooks

Webhooks let your application receive real-time notifications when lead events happen, instead of polling the API.

How it works​

  1. Subscribe to one or more event types by creating a webhook subscription (POST /v1/webhook-subscriptions).
  2. ABC Sales AI sends an HTTP POST to your callback URL whenever a subscribed event fires.
  3. Your endpoint verifies the signature and responds with a 2xx status to acknowledge receipt.

Event types​

You can subscribe to these event types:

Event typeFires when
leads_createdA new lead is created
leads_updatedA lead is updated
lead_tag_addedA lead gains one or more tags it did not already carry
leads_deletedA lead is deleted
leads_escalation_requestedA lead requests escalation to a human
conversation_createdA new conversation is created

Fetch the current list programmatically via GET /v1/webhook-subscriptions/events.

Managing subscriptions​

ActionEndpoint
List event typesGET /v1/webhook-subscriptions/events
Create a subscriptionPOST /v1/webhook-subscriptions
Update a subscriptionPUT /v1/webhook-subscriptions/{subscriptionId}
Delete a subscriptionDELETE /v1/webhook-subscriptions/{subscriptionId}
Regenerate signing secretPOST /v1/webhook-subscriptions/{subscriptionId}/regenerate-secret

See the API Reference for exact request and response schemas.

Filter deliveries by lead tags​

Set lead_tag_filter when creating or updating a subscription to receive events only for leads carrying any listed tag. Matching is case-insensitive, with at most 20 entries. For example, {"lead_tag_filter":["VIP","Interested"]} matches either tag, not necessarily both.

  • On creation, omit the field or send [] for no tag restriction.
  • On update, omitting the field preserves the existing filter; sending [] clears it.
  • The filter checks tags carried by the lead when the delivery is built, not just the tags added by the triggering event.
  • It is a delivery filter, not an authorization boundary. Do not assume this API option is exposed in the web interface.

Delivery headers​

Every webhook request includes these headers:

HeaderDescription
X-AbcSalesAI-SignatureHMAC-SHA256 signature of the raw request body (hex). See below.
X-AbcSalesAI-EventThe event type that triggered this delivery.
X-AbcSalesAI-DeliveryUnique delivery ID (useful for logging and idempotency).
X-AbcSalesAI-TimestampUnix timestamp of the delivery.
Content-Typeapplication/json
User-AgentEngagematic-Webhook-Dispatcher/1.0

Payload​

The request body is JSON with this shape (a lead event example):

{
"event_type": "leads_created",
"event_data": {},
"lead": {
"id": "external-lead-id",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+60123456789",
"source": "whatsapp",
"timezone": "Asia/Kuala_Lumpur",
"locale": "en",
"currency": "MYR",
"tags": ["new"],
"custom_fields": {},
"created_at": "2026-01-01T08:00:00Z"
}
}

event_data carries event-specific detail. conversation_created adds a top-level conversation object (below), lead_tag_added adds tags_added, and leads_escalation_requested may add escalation_reason and escalation_topic.

lead_tag_added​

{
"event_type": "lead_tag_added",
"event_data": { "company_id": 123, "lead_id": 456, "tag_names": ["VIP"] },
"lead": { "id": "external-lead-id", "name": "Jane Doe", "tags": ["new", "VIP"] },
"tags_added": ["VIP"]
}

The lead object is shortened here. tags_added identifies newly gained tags; lead.tags is the lead's current tag set. Reapplying an existing tag is not a new addition, and removing a tag is not a lead_tag_added event. Tags removed before the delivery is built are not reported as newly added when the current tag set can be read.

conversation_created​

{
"event_type": "conversation_created",
"event_data": { "company_id": 123, "lead_id": 456, "conversation_id": 789 },
"lead": { "id": "external-lead-id", "name": "Jane Doe" },
"conversation": {
"lead_id": 456,
"company_id": 123,
"conversation_id": 789,
"message": "Hi Jane! Yes, 3pm is still free. Shall I book it for you?",
"sent_by": "agent",
"message_source": "ai_agent"
}
}

(The lead object is shortened here; it has the same fields as in the lead events above.)

FieldDescription
messageThe message text. Can be empty, for example for an attachment-only message or a silent AI turn (below).
sent_bylead for a message from the lead, agent for anything sent on your side (AI or human).
message_sourceWhere the message came from, when that is recorded (see below). Optional: left out when there is no source, never null.
attachmentsFiles on the message. Fetch each download_url with your API key. Left out when there are none.

message_source values you may see:

ValueMeaning
ai_agentA reply written by your AI Employee
ai_silentThe AI read the conversation and deliberately chose not to reply (for example, its instructions say to stay quiet after a human takes over). Nothing was sent to the lead, and message is empty.
ai_reply_failedThe AI Employee turn failed without sending a customer-facing reply. This is not an intentional silence decision.
human_agentA reply from someone on your team in the ABC Sales AI inbox
scheduled_messageA message sent by an automation or a scheduled message
ai_managerA message the AI Manager sent to the lead on a team member's instruction
whatsapp_business_app, facebook_messenger_app, instagram_appA message your team sent from the WhatsApp Business, Messenger or Instagram app, copied into ABC Sales AI
facebook_comment, instagram_commentA Facebook or Instagram comment

Not every message records a source, so do not rely on message_source to tell AI and human replies apart. New values may be added; treat a value you do not recognise as an ordinary message.

Skip AI turns that sent no reply

conversation_created also fires for intentional silence and failed AI turns. If your integration only wants customer-facing messages, exclude events whose message_source is ai_silent or ai_reply_failed. Neither marker confirms delivery. Do not automatically resend a message solely because one of these markers arrives.

message_source remains optional; existing consumers can ignore it, and integrations should tolerate new values. The webhook does not include an internal no_reply_reason field.

Verifying signatures​

Each subscription has a signing secret: a 64-character hex string, returned when you create the subscription and rotatable via the regenerate-secret endpoint.

The X-AbcSalesAI-Signature header is the HMAC-SHA256 of the raw request body, keyed with your signing secret, hex-encoded. Compute the same value and compare in constant time. Use the raw bytes of the body, before any JSON parsing or re-serialization.

const crypto = require('crypto');

function isValidSignature(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody) // the raw request body, not the parsed object
.digest('hex');
// constant-time comparison
return (
signatureHeader.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected))
);
}

Reject any request whose signature does not match.

Retries​

If your endpoint does not respond with a 2xx status (or times out), ABC Sales AI retries delivery:

  • Timeout: each attempt waits up to 10 seconds for your response.
  • Success: any 2xx status. Anything else (including timeouts and network errors) is a failure.
  • Retries: up to 5 retries after the initial attempt, with exponential backoff of 1, 2, 4, 8, then 16 minutes.
  • After the final retry fails, the delivery is marked failed and not retried again.

Make your handler idempotent (use X-AbcSalesAI-Delivery to de-duplicate), since a delivery can arrive more than once.