You can configure your Zeus account to send webhook events to a specific endpoint when a lead is either created or updated.
To enable webhooks, follow these steps:
Create an endpoint that can receive the data from Zeus.
Provide the Zeus team with the endpoint URL so they can set up the events to be sent to that endpoint
Once the webhook is enabled, Zeus will send a POST request to your endpoint.
All webhook requests from Zeus are signed using Ed25519 to ensure authenticity and integrity. You can use the included headers to verify that the request originated from Zeus and that the payload has not been tampered with.
Each webhook request includes the following headers:
Header | Description |
---|---|
x-webhook-timestamp | ISO-8601 timestamp of when the webhook was generated. |
x-webhook-signature | Base64-encoded Ed25519 signature of the request body. |
x-webhook-key-id | Identifier for the public key used to verify the signature. |
Verifying the Signature To verify a webhook and ensure it’s both authentic and recent:
Retrieve the x-webhook-timestamp, x-webhook-signature, and x-webhook-key-id headers from the incoming request.
Verify the timestamp is recent (for example, within 5 minutes).
Concatenate the timestamp, a period (.), and the exact raw body bytes.
Use the Ed25519 algorithm and the provided public key to verify the signature.
Example verification (Node.js / Express):
import { verify } from 'crypto';
import express from 'express';
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.post('/webhook', (req, res) => {
const signature = req.header('x-webhook-signature');
const timestamp = req.header('x-webhook-timestamp');
const keyId = req.header('x-webhook-key-id');
const publicKey = getPublicKeyById(keyId);
const rawBody = req.body;
const FIVE_MINUTES = 5 * 60 * 1000;
const timestampMs = Date.parse(timestamp);
const now = Date.now();
if (isNaN(timestampMs) || Math.abs(now - timestampMs) > FIVE_MINUTES) {
console.error('Webhook timestamp too old or invalid');
return res.status(400).send('Invalid timestamp');
}
const dataToVerify = Buffer.concat([
Buffer.from(timestamp + '.', 'utf8'),
rawBody,
]);
const isValid = verify(
'ed25519',
dataToVerify,
publicKey,
Buffer.from(signature, 'base64')
);
if (!isValid) {
console.error('Invalid webhook signature');
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody.toString());
console.log('✅ Verified webhook:', event);
res.sendStatus(200);
});
Always verify the timestamp first before signature verification to reject obviously stale or replayed requests early.
Use the raw request body (as a Buffer) to ensure signature verification is based on the exact bytes sent by Zeus.
Recommended time window: ±5 minutes, but you can adjust it based on your tolerance for clock drift or retry policies.
Every webhook request from Zeus contains a unique header: x-webhook-event-id This value acts as an idempotency key, ensuring that you can safely process each event exactly once, even if Zeus retries sending it.
Why It’s Important? Webhooks may be delivered multiple times in certain cases, such as:
Network errors or timeouts
Server errors on your endpoint
Retries from Zeus to guarantee delivery
Processing the same webhook multiple times could cause unintended duplicate actions (e.g., creating the same lead twice). The x-webhook-event-id allows you to detect and skip duplicates.
When receiving a webhook:
Retrieve the x-webhook-event-id header.
Check your data store (database, cache, etc.) to see if this event ID has already been processed.
If it has, skip processing and respond with 200 OK to acknowledge receipt. If it hasn’t, process the event and save the event ID as processed.
Once the webhook is enabled, Zeuswill send a POST request to your endpoint with event data in JSON format. The payload will include the following key fields:
type: This field indicates the event type, which can be "lead.created" or "lead.updated".
lead: This field contains detailed information about the lead.
The lead data includes:
id: The unique identifier for the event
lead.id: A unique identified for the lead, that is consistent along all events fired for that specific lead
source: The origin of the lead (e.g., WhatsApp).
status: The status of the lead (e.g., “In Progress”, "Successful").
contact: Information about the lead's contact info, including the contact's firstName, lastName, and email. In case you have some custom fields set up on the contact level, those will be passed within the request as well.
communicationChannels: The communication channels used by the lead (e.g., whatsapp, or twilio-sms).
messages: An array of messages exchanged between the lead and the system, each containing id, type (e.g., Incoming or Outgoing), content, and timestamps.
attributes: Additional lead attributes such as the deposit amount, mortgage type, mortgage amount, call availability, mortgage timeline, and any borrowing blockers. In case you have some custom lead attributes set up on the contact level, those will be passed within the request as well.
lead.created event
This is the example of an event that gets triggered upon lead creation. The "type":"lead.created" part determines this is a new lead.
{
"id": "2532****-****-4633-****-ffb****f1262",
"lead": {
"id": "65c3****-****-****-91c7-****fdad7eb2",
"source": "test",
"status": "In Progress",
"contact": {
"id": "55***d80-***3-**f3-**f6-0254a5****d7",
"attributes": {},
"communicationChannels": [
{
"id": "5f****d-***3-4***-a***-******f53d2b",
"type": "test",
"externalId": "ee2****-5***-4dfe-b***-9cec3d******"
}
]
},
"messages": [
{
"id": "12****1a-****-****-****-56****76c062",
"type": "Outgoing",
"content": "Hello and welcome to Zeus! We help you scale your sales processes with the power of AI! \n\nTo get started, can you please share your full name with me?",
"updatedAt": "2025-01-05T19:40:01.185Z",
"communicationChannelId": "5fb2****-****-****-****-a8f****53d2b"
}
],
"createdAt": "2025-01-05T19:40:01.185Z",
"updatedAt": "2025-01-05T19:40:01.185Z",
"attributes": {}
},
"type": "lead.created"
}
lead.updated event
This is the example of an event that gets triggered on lead update, and it has the "type":"lead.updated" identifier. A separate lead.updated event is sent every time there is an update on the contact or lead attributes. Please find an example of the webhook event body below.
{
"id": "8693****-****-****-****-c341cf61****",
"lead": {
"id": "68dd****-****-****-****-7d5a20a3****",
"source": "test",
"status": "In Progress",
"contact": {
"id": "2906****-****-****-****-5bd3fa3****",
"attributes": {
"email": null,
"lastName": null,
"firstName": null
},
"communicationChannels": [
{
"id": "9620****-****-****-9d5f-883c3a93****",
"type": "test",
"externalId": "1f4d****-****-****-****-8169bf3a****"
}
]
},
"messages": [
{
"id": "8f6c****-****-****-****-895120a5****",
"type": "Outgoing",
"content": "Thanks for checking out Zeus, feel free to ask me any questions.\n\nTo start, it would be great to understand your industry, so I tailor the solution according to your needs.",
"updatedAt": "2025-01-05T20:12:29.213Z",
"communicationChannelId": "9620****-****-****-****-883c3a93****"
},
{
"id": "59f9****-****-****-****-ff8fa199****",
"type": "Incoming",
"content": "I am in the travel industry",
"updatedAt": "2025-01-06T07:40:24.353Z",
"communicationChannelId": "9620****-****-****-****-883c3a93****"
},
{
"id": "9d36****-****-****-****-10cb595****",
"type": "Outgoing",
"content": "Great to know you're in the travel industry! What are some of your commercial goals? For instance, are you looking to increase bookings, reduce time spent on customer inquiries, or perhaps minimise time wasted on non-serious leads?",
"updatedAt": "2025-01-06T07:40:24.353Z",
"communicationChannelId": "962****-****-****-****-883c3a93****"
}
],
"createdAt": "2025-01-05T20:12:29.069Z",
"updatedAt": "2025-01-06T07:40:24.353Z",
"attributes": {
"industry": "travel",
"commercialGoals": null,
"websiteVisitors": null,
"interestedInDemo": null,
"averageCustomerValue": null
}
},
"type": "lead.updated"
}
lead.updated event for successful/qualified leads
With each attribute update, a new lead.updated event will get fired. However, once all of the required attributes have been collected, the lead.updated event will have the lead.status as "Successful" instead of "In Progress" as it is the case with all the preceding leads.
{
"id": "211*****-****-****-****-227c1****c7",
"lead": {
"id": "68d****-b741-****-****-7d5a20a****",
"source": "test",
"status": "Successful",
"contact": {
"id": "290****-****-****-be55-****fa33f697",
"attributes": {
"email": null,
"lastName": null,
"firstName": null
},
"communicationChannels": [
{
"id": "9620****-****-43bc-****-883c3a9364a7",
"type": "test",
"externalId": "1f4****-****-****-****-8169****379"
}
]
},
"messages": [
{
"id": "8f6****-****-****-a5af-89512****9e",
"type": "Outgoing",
"content": "Thanks for checking out Zeus, feel free to ask me any questions.\n\nTo start, it would be great to understand your industry, so I tailor the solution according to your needs.",
"updatedAt": "2025-01-05T20:12:29.213Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "59f9****-****-****-8116-ff8fa19****",
"type": "Incoming",
"content": "I am in the travel industry",
"updatedAt": "2025-01-06T07:40:24.353Z",
"communicationChannelId": "96208170-64b6-43bc-9d5f-883c3a9364a7"
},
{
"id": "9d36****-d85f-****-****-10cb595e****",
"type": "Outgoing",
"content": "Great to know you're in the travel industry! What are some of your commercial goals? For instance, are you looking to increase bookings, reduce time spent on customer inquiries, or perhaps minimise time wasted on non-serious leads?",
"updatedAt": "2025-01-06T07:40:35.723Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "bfef****-****-****-bf81-9f3500fa****",
"type": "Incoming",
"content": "Where are you based?",
"updatedAt": "2025-01-06T07:43:22.528Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "****d774-****-****-****-d96fd28****7",
"type": "Outgoing",
"content": "Zeus is based in the UK. Now, could you share more about your commercial goals in the travel industry?",
"updatedAt": "2025-01-06T07:43:33.095Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "f9e0****-****-413b-****-67665f92****",
"type": "Incoming",
"content": "More customers",
"updatedAt": "2025-01-09T11:10:12.034Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "7154****-e2bc-****-****-a88aa362****",
"type": "Outgoing",
"content": "That's a fantastic goal! There might be an opportunity to streamline your sales process and help you earn more money. Could you tell me the average number of monthly visitors to your website?",
"updatedAt": "2025-01-09T11:10:23.733Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "78c2****-****-****-9b2a-f41ac06d****",
"type": "Incoming",
"content": "circa 5000",
"updatedAt": "2025-01-09T11:10:31.551Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "35b2****-2cfd-42b0-****-ca1a3613****",
"type": "Outgoing",
"content": "Thank you for sharing that. With around 5000 visitors each month, what would you say is the average value of a customer for your business?",
"updatedAt": "2025-01-09T11:10:43.756Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "093****-3956-****-****-c8126165****",
"type": "Incoming",
"content": "£2000",
"updatedAt": "2025-01-09T11:11:08.192Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "3bfc****-****-4bcc-****-762cb163****",
"type": "Outgoing",
"content": "Great, thank you. For your industry, we anticipate that conversational AI can boost your website conversions by 0.7%. This means our solution could potentially bring you an additional £70,000 in revenue per month. Would you like to explore how our tool can specifically benefit your business?",
"updatedAt": "2025-01-09T11:11:18.669Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "84e2****-****-4dfe-bcad-2b6175ed****",
"type": "Incoming",
"content": "yes please",
"updatedAt": "2025-01-09T11:11:40.164Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "29ed****-****-4176-****-d5b32e7b****",
"type": "Outgoing",
"content": "Thanks for checking out our demo! \nIt looks like there is an exciting commercial opportunity to add conversational AI to your business. We'd love to discuss this further on a call, does any of this availability work for you https://app.apollo.io/#/meet/openfi/30-min\n\nIf you have any questions about the demo or our solutions, feel free to ask!",
"updatedAt": "2025-01-09T11:11:50.400Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "3e40****-fc61-****-ae4e-752c6f****",
"type": "Incoming",
"content": "Okay thanks",
"updatedAt": "2025-01-09T11:11:57.746Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "92f7****-04b5-****-****-2382520b****",
"type": "Outgoing",
"content": "You're welcome! If you have any more questions or need further assistance, feel free to reach out. Have a great day!",
"updatedAt": "2025-01-09T11:12:27.759Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "9533****-569a-****-****-e3702280****",
"type": "Incoming",
"content": "I actually checked now and only have 2000 visitors/month",
"updatedAt": "2025-01-09T11:12:29.027Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
},
{
"id": "9ac4v-****-4924-****-96cb1be0****",
"type": "Outgoing",
"content": "No problem, I've updated your website visitors to 2000 per month. If there's anything else you'd like to adjust or inquire about, just let me know!",
"updatedAt": "2025-01-09T11:12:29.027Z",
"communicationChannelId": "9620****-****-****-9d5f-883c3a93****"
}
],
"createdAt": "2025-01-05T20:12:29.069Z",
"updatedAt": "2025-01-09T11:12:29.027Z",
"attributes": {
"industry": "travel",
"commercialGoals": "More customers",
"websiteVisitors": "2000",
"interestedInDemo": "yes please",
"averageCustomerValue": "£2000"
}
},
"type": "lead.updated"
}
lead.outgoing-message-created event
With each new message sent during the course of the chat, lead.outgoing-message-created event will get fired. This event allows you to be able to consume all the conversation updates in terms of the back and forth messages sent, even if there have been no updates on the lead or contact attributes. The event is getting fired both for "In Progress" and "Successful" leads.
{
"id": "c1eb****-****-****-****-6bf35408****",
"lead": {
"id": "68dd****-****-****-b525-7d5a20a3****",
"source": "test",
"status": "In Progress",
"contact": {
"id": "2906****-****-****-****-5bd3fa33****",
"attributes": {
"email": null,
"lastName": null,
"firstName": null
},
"communicationChannels": [
{
"id": "9620****-****-****-****-883c3a93****",
"type": "test",
"externalId": "1f4d****-****-****-****-8169bf3a****"
}
]
},
"messages": [
{
"id": "8f6c****-****-****-****-895120a5****",
"type": "Outgoing",
"content": "Thanks for checking out Zeus, feel free to ask me any questions.\n\nTo start, it would be great to understand your industry, so I tailor the solution according to your needs.",
"updatedAt": "2025-01-05T20:12:29.213Z",
"communicationChannelId": "9620****-****-****-****-883c3a93****"
},
{
"id": "59f9****-****-****-****-ff8fa199****",
"type": "Incoming",
"content": "I am in the travel industry",
"updatedAt": "2025-01-06T07:40:24.353Z",
"communicationChannelId": "9620****-****-****-****-883c3a93****"
},
{
"id": "9d36****-****-****-****-10cb595e****",
"type": "Outgoing",
"content": "Great to know you're in the travel industry! What are some of your commercial goals? For instance, are you looking to increase bookings, reduce time spent on customer inquiries, or perhaps minimise time wasted on non-serious leads?",
"updatedAt": "2025-01-06T07:40:35.723Z",
"communicationChannelId": "9620****-****-****-****-883c3a93****"
},
{
"id": "bfef****-****-****-****-9f3500fa****",
"type": "Incoming",
"content": "Where are you based?",
"updatedAt": "2025-01-06T07:43:22.528Z",
"communicationChannelId": "9620****-****-****-****-883c3a93****"
},
{
"id": "a675****-****-****-****-d96fd281****",
"type": "Outgoing",
"content": "Zeus is based in the UK. Now, could you share more about your commercial goals in the travel industry?",
"updatedAt": "2025-01-06T07:43:33.095Z",
"communicationChannelId": "962****-****-****-****-883c3a93****"
}
],
"createdAt": "2025-01-05T20:12:29.069Z",
"updatedAt": "2025-01-06T07:40:24.353Z",
"attributes": {
"industry": "travel",
"commercialGoals": null,
"websiteVisitors": null,
"interestedInDemo": null,
"averageCustomerValue": null
}
},
"type": "lead.outgoing-message-created"
}