Overview
Message webhooks push status updates to a URL you control whenever a message reaches a terminal state. Configure them in your workspace settings — Tuco handles delivery automatically.Webhook calls are POST requests with a JSON body. All events share the same
base fields; each event type adds its own specific fields. Outgoing message
events are fired when a message is sent (accepted by Tuco), not when
delivery is confirmed on the recipient’s device.
Event types
message.ai_draft — review AI drafts from your own tools
When your knowledge base runs in Copilot mode, every AI-drafted reply fires
message.ai_draft before anything is sent. The payload carries the draft text
on message (string) and a data.replyId handle:
action is approve (claims the draft and sends it), reject, or
mark_sent (claim only — you already delivered the text yourself). The claim
is atomic and first-action-wins across every channel, so an approval from
Telegram, the dashboard, and your API call can never double-send. A 409
response means another channel already actioned the draft.
Every resolution then fires message.ai_draft_approved or
message.ai_draft_rejected — regardless of which channel actioned it —
with data.approvalChannel (dashboard, api, telegram, telegram_edit,
telegram_retry, unibox_direct_send) so your workflow can log who approved
what, including approvals that never touched your tools.
message.reply
Sent when a new reply from a lead or contact is recorded. Use it to update your CRM, trigger automations, or log conversations.
When a contact owner is set (
contactOwnerEmail), reply email notifications are routed only to the contact owner rather than all workspace users.data.reply is identical to one item in GET /api/replies (same keys, same types). Reply text is top-level message (string). parentMessages is always present (array; possibly empty).
Top-level fields
message (reply text only)
For message.reply, message is the reply text only (string) — what the lead or contact replied with.
data.reply — matches GET /api/replies item exactly
data.reply has the exact same shape as one element of GET /api/replies replies array:
Plus
recentReplies — the last 10 inbound replies from this lead (array, possibly empty):
Example payload:
Reply with a photo/file
When the lead replies with an image or file, the same payload additionally carriesattachmentDownloadUrls + attachmentNames (top-level and inside data.reply / its recentReplies). GET a download URL to store the bytes — no Authorization header needed, the signature is the capability:
message.sent
Sent when a message has been sent (accepted by Tuco). This is the only
outgoing-message event fired to your webhook; Tuco does not fire a separate
event when delivery is confirmed on the recipient’s device.
Payload: The message field is the full message object (not a string), with _id, message, messageType, status, fromLineId, recipientPhone, recipientEmail, recipientName, leadId, workspaceId, createdByUserId, sentAt, createdAt, updatedAt, etc. Top-level also includes leadId, lead (full lead when available), campaignId, campaignName, and integration IDs.
Full example body
string
"message.sent"string
Tuco message ID
object
Full message document (not string)
string
Lead ID when known
object
Full lead when available
string | null
GHL contact ID for integrations
string | null
GHL location ID for integrations
string
When the webhook was fired (ISO UTC)
message.reaction
Sent when a lead or contact reacts to an existing message in a conversation.
Tuco treats reactions differently from replies:
- they are stored as reactions on the original message
- they do not become a normal text reply
- they do not trigger AI reply handling
Top-level fields
Example payload
message.failed
Sent when a message has exhausted all retry attempts or encountered an
unrecoverable error (e.g. availability API failure). The message field is the full message object (includes errorMessage when set). data may include error (reason string).
Full example body
string
"message.failed"object
Full message document (includes errorMessage)
string
Human-readable explanation of the failure
message.fallback
Sent when Tuco determines that the primary channel (e.g. iMessage) is not
available for this recipient. This is a business outcome, not a technical
failure. The message field is the full message object. data includes reason and optionally checkedAddresses (string[]).
Full example body
string
"message.fallback"object
Full message document (status fallback)
string
Human-readable explanation
string[]
All addresses Tuco checked for iMessage availability before deciding fallback.
Useful for debugging and auditing.
Common patterns
Auto-send SMS
Trigger an SMS via your own Twilio/fallback when you receive
message.fallback.
Or enable Tuco’s built-in fallback SMS on the workspace.Tag lead in CRM
Flag the lead as “no iMessage” so future campaigns pick the right channel
automatically.
message.opened
Sent when the recipient has opened/read the message (read receipt). Fired where the channel supports read receipts. Use it to track engagement or trigger follow-ups.
Payload: Same top-level shape as message.sent. The message field is the full message object (not a string), including readAt (ISO 8601 UTC) when the message was read. data may include readAt.
Full example body
string
"message.opened"object
Full message document including readAt (ISO UTC when read)
string
When the message was opened (ISO UTC)
Shared fields (all events)
Every webhook payload includes these base fields:Headers & signature verification
Every webhook POST includes these headers:
Verify signatures to confirm webhooks actually came from Tuco:
Consuming webhooks safely
1
Use messageId + event as your key
Make updates idempotent. If you’ve already processed a
(messageId, event) pair, ignore duplicates.2
Move status forward only
Only update your local status in the forward direction:
queued → sent → delivered. Never move backwards.3
Return 200 quickly
Tuco expects a
2xx response within a few seconds. Do heavy processing
asynchronously after acknowledging the webhook.4
Log workspaceId and leadId
These fields let you trace events back to the right tenant and contact
in multi-workspace setups.