Turbo Notify: WhatsApp for AI agents

com.turbonotifyPublished by turbonotify.comOfficial publisherv0.2.1Updated Nov 6, 2026

Send and receive WhatsApp messages from your AI agent, on your own number.

The publisher's own server: Turbo Notify: WhatsApp for AI agents

VerifiedStreamable HTTPWeb executableProductivity & WorkflowCommunication & Collaboration

Overview

AI-generated overview

Remote MCP server that lets an AI agent send and receive WhatsApp messages on an organization's own numbers, including groups, media and calendar invites.

What you can do
  • Send text, media by URL, locations, contact cards, calendar invites and link-button messages to a number, group or contact
  • Reply to, edit, react to, mark read, delete and show typing on messages
  • Read conversations: list and fetch messages, delivery status, receipts, reactions and calendar responses
  • Manage contacts and groups: list, fetch details, profile pictures, refresh names
  • Manage numbers: list, inspect, connect or disconnect, and check message, contact and usage quotas
  • Poll an event feed for inbound messages and other changes without hosting a webhook
When to use it
Useful when an agent should talk to customers or teammates over WhatsApp on a business number, for example a virtual attendant that answers inbound chats, sends notifications or media, and tracks delivery. Also fits workflows that need to read WhatsApp conversations and react to new messages.
Requirements
Remote streamable HTTP endpoint; no local package. A Turbo Notify account, or an API key sent as an Authorization Bearer header. Each messaging tool takes a number_alias, defaulting to main. Some features, such as read receipts, need a plan that includes them.
Before you install
The server sends real WhatsApp messages from your number and can delete them for everyone, which is irreversible; confirm with a human before deleting or messaging people. It can edit and cancel calendar events and disconnect numbers. Quotas are per number and billed per cycle; disconnecting a number does not stop billing. Credentials are passed as an Authorization header, and message content and contact data go to Turbo Notify.

Installation

In SourceWeft

  1. Open Turbo Notify: WhatsApp for AI agents in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/com.turbonotify%2Fturbo-notify/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config.

{
  "mcpServers": {
    "turbo-notify-whatsapp-for-ai-agents": {
      "type": "http",
      "url": "https://mcp.turbonotify.com/"
    }
  }
}

README

Turbo Notify Agent Skills

Skills that put the Turbo Notify MCP server to work inside your AI agent: Claude Code, Claude Desktop and Cowork, Codex, the Antigravity CLI and the Gemini CLI, and any other agent that reads the Agent Skills format.

Skills

SkillWhat it does
virtual-attendantServes a business's customers on WhatsApp: answers in the customer's language, remembers each customer, calls a person when it should, and takes commands from administrators and attendants on WhatsApp itself.

Customer guides (Portuguese and English): WhatsApp virtual attendant, Agent skills and AI agents, with one page per agent.

Install

AgentCommands
Claude Codeclaude plugin marketplace add turbo-notify/agent-skills then claude plugin install turbo-notify@turbo-notify
Claude Desktop and CoworkCustomize, Plugins, Add, Add marketplace, Add from a repository: turbo-notify/agent-skills; then Discover, turbo-notify, Add
Codexcodex plugin marketplace add turbo-notify/agent-skills then codex plugin add turbo-notify@turbo-notify
Antigravityagy plugin install https://github.com/turbo-notify/agent-skills
Gemini CLIgemini extensions install https://github.com/turbo-notify/agent-skills
Hermes Agenthermes skills install turbo-notify/agent-skills/skills/virtual-attendant, then add the MCP server https://mcp.turbonotify.com in Hermes
OpenCodenpx skills add turbo-notify/agent-skills -a opencode (project folder .agents/skills/, or -g for ~/.config/opencode/skills/), then opencode mcp add --global --url https://mcp.turbonotify.com turbo-notify and opencode mcp auth turbo-notify to sign in. Guide: Turbo Notify in OpenCode
Other agents that read the Agent Skills format (Cursor, Copilot and more)npx skills add turbo-notify/agent-skills, then add the MCP server https://mcp.turbonotify.com in that agent's own settings

The unattended runner exists for Claude Code and Codex only. In the other agents the attendant runs in assisted mode: a session you keep open, where a person approves each draft.

The plugin rows above already declare the Turbo Notify MCP server (https://mcp.turbonotify.com), so there is nothing to add. Sign in with your Turbo Notify account the first time the agent uses it (Claude Code: /mcp; Codex: codex mcp login turbo-notify; Antigravity: Agent settings, Customizations, Authenticate next to the server), then ask it to "set up the Turbo Notify virtual attendant". No account yet? Create one at https://dashboard.turbonotify.com/auth/signup?locale=en. For anything that runs alone, use an API key instead (see the guide for your agent in skills/virtual-attendant/references/).

Update

The skill checks its version on every cycle and messages the administrators when a new one exists, with the command for the agent in use.

AgentCommand
Claude Codeclaude plugin marketplace update turbo-notify then claude plugin update turbo-notify@turbo-notify
Claude Desktop and CoworkThe marketplace syncs from the repository; if a new version does not show up, remove the marketplace and add it again
Codexcodex plugin marketplace upgrade then codex plugin add turbo-notify@turbo-notify
AntigravityRun the install command again
Gemini CLIgemini extensions update turbo-notify

What it reaches

  • The Turbo Notify MCP server at https://mcp.turbonotify.com, which the plugin declares in .mcp.json, mcp.json and mcp_config.json (one file for each family of agents; none holds a key or a header). You sign in with your account, or you connect your own with an API key. It runs with your account or API key permissions.
  • One folder on your computer (by default ~/.turbo-notify/attendant) holding, for each customer, the conversation (kept 90 days by default, then deleted), short summaries of older parts of it, and a file with the facts that customer gave (name, preferences, what they asked for), plus what you taught. Turbo Notify gets no copy. /forget erases one customer's data, and the retention is a number you can change in memory-policy.json.
  • The Turbo Notify API at https://api.turbonotify.com, called by the helper with your API key when the unattended runner or a console command sends a message.
  • One network address besides Turbo Notify: raw.githubusercontent.com/turbo-notify/agent-skills/main/latest.json, read about once an hour (and again after 10 minutes if a check fails) for the version check. A failure changes nothing.
  • Your other MCP servers only if you allow each one during setup.

What it runs and what it sends

  • It runs the tn_assist helper with python3. The helper uses only the standard library and installs nothing.
  • In the unattended runner (Claude Code and Codex), it starts your own claude or codex command once per customer, in a separate process with no tools, to write that customer's answer. The process gets that customer's messages, their file and the business knowledge that matches the message (the company, tone and hand-off files, plus up to three more picked by the words of the message), and nothing else.
  • The text of a customer's message goes to the model of the agent you use, under your account with that agent. It goes nowhere else.
  • The unattended runner reads your API key from the TURBO_NOTIFY_API_KEY variable you set for it and sends it only to api.turbonotify.com, as the sign-in of each request. It is never passed to the process that writes a customer's answer.
  • Answers go out through the Turbo Notify API with your API key, to the customer who wrote. A check before every send stops another customer's number or name, non-public text and credentials.
  • The first answer of each chat tells the customer that it comes from an AI. The line is added by the helper, not by the model.
  • A customer who only says thanks, "ok" or sends a thumbs up gets no answer and is not handed to a person. The model has to ask for that, and the helper agrees only when the message is nothing but a thanks or an acknowledgement (no question mark, no number, a few words from a fixed list). Anything more is answered, or goes to a person.
  • A customer goes to a person when the answer is not in what you taught (a price, stock, a deadline, a promise, a policy), when they ask for one or are upset, when the request is outside the business, when a payment problem, a refund or exchange, a complaint or a legal matter comes up, or when they ask the assistant to promise or confirm something in writing. A plain question about a rule you wrote, such as the exchange window, is answered.
  • If the model's answer cannot be read, the helper asks again once with a line naming what was wrong, and a second failure in a row sends the customer a message saying a person will continue.

Layout

.claude-plugin/        plugin and marketplace manifests (Claude Code, Codex).codex-plugin/         the manifest Codex reads for the name, logo and listing textplugin.json            manifest for Antigravitygemini-extension.json  manifest for the Gemini CLI (the connection to the server).mcp.json              the Turbo Notify MCP server, as Claude Code, Claude Desktop, Cowork and Codex read itmcp.json               the same server, as the Agent Plugins format reads it (Copilot, Cursor)mcp_config.json        the same server, as Antigravity reads itassets/icon.png        the plugin iconagents/                the tool-less agent that answers a customerskills/virtual-attendant/  SKILL.md             the entry point the agent reads  references/          one short file per topic (setup, tick, console, memory, privacy, ...)  assets/templates/    interview templates (pt, en) and the memory policy  scripts/tn_assist/   the helper: routing, memory, egress check, runner (Python 3.9+, no dependencies)tools/release.py       version consistency check and synctests/                 unit, property and security tests (Poetry, development only)evals/attendant/       live evaluation of a customer's turn on a real model, with a made-up shop

The helper holds the guarantees the model cannot be trusted with: who is who is decided by code from the registered numbers, a customer's turn runs without tools, every reply passes an egress check before it can leave, and the data never leaves the workspace folder.

Development

poetry installpoetry run pytestpoetry run ruff check . && poetry run ruff format --check .poetry run mypypython3 tools/release.py check

The live evaluation needs your own claude command, logged in. It asks 40 questions that the knowledge cannot answer and 11 that it can, and plays seven conversations (some end with a thank you, which is owed no answer). It checks every reply for a price, a date, a time, a quantity, a discount or a promise the knowledge does not state, for the language of the reply, and that a thank you is left alone while an "ok" that is really a question is answered. Two last checks cut the model's answer in half to see what the runner does with an answer it cannot read, and play an "ok" after an offer ("quer que eu chame alguém?") to see that it is answered, not left alone. Nothing in it is real data.

poetry run python -m evals.attendant run                # writes evals/attendant/results/<date>-<label>/poetry run python -m evals.attendant run --set refusal  # only the check on an unreadable answerpoetry run python -m evals.attendant run --set offers   # only the check on an ok that accepts an offerTN_EVALS_LIVE=1 poetry run pytest evals                 # the same, as tests (skipped otherwise)

Releases bump VERSION, latest.json, the five manifests, the helper and pyproject versions and CHANGELOG.md together (tools/release.py sync), then move the v1 tag, which stays available for anyone who wants to pin to 1.x (...agent-skills#v1, or --ref v1 in Codex).

License

MIT. See LICENSE.

Source: README.md at commit 8e55d26

Tools

36

send_text

Send a text message

Send a WhatsApp text message. Set exactly one recipient: `number`, `group_id`, or `contact_id`. Writing `@<number>` in the text mentions that person, for example `@5511999999999`. The platform detects it automatically; there is no separate mentions argument. This is also how to answer the message you just received: a normal message to its sender, as a person does in a chat, with no quote. Quote (`quoted_message_id`, or `reply_to_message`) only an earlier message that your answer depends on. A text message you sent can be corrected later with `edit_text_message`. Like a person opening the chat, the number first appears online and its unread messages in that conversation are marked as read, then the message is sent. Opening the conversation this way is automatic and uses no quota of its own. Returns the new `message_id` and its initial status. Delivery happens asynchronously; poll `get_message_status` or watch `list_events` for the outcome.

Parameterstextrequirednumbergroup_idcontact_idnumber_aliasidempotency_keyquoted_message_id

send_media

Send an image, video, audio, document or sticker

Send media by URL: an image, video, audio clip, document or sticker. Set exactly one recipient: `number`, `group_id`, or `contact_id`. `url` must still be reachable when the send happens: Turbo Notify fetches the bytes from it. A copy is kept in the organization's own object storage afterwards when that is configured, which never blocks the send and is not required for one. `caption` applies to `image`, `video` and `document` only; audio and stickers have nowhere to render one. `filename` applies to `document` only. Pairing either with a kind that cannot carry it is rejected here, before anything is sent. After sending, only the caption of an image, video or document can be changed, with `edit_media_caption`. The file itself, an audio clip and a sticker cannot be edited: `delete_message` is the only change for them. `mime_type` is optional and does not change how the message is sent. The type WhatsApp receives comes from what the URL's own server serves, so point `url` at a file that server labels correctly.

Parametersurlrequiredkindrequirednumbercaptionfilenamegroup_idmime_typecontact_idnumber_aliasidempotency_keyquoted_message_id

send_location

Send a map location

Send a location pin. Set exactly one recipient: `number`, `group_id`, or `contact_id`. `latitude` is between -90 and 90, `longitude` between -180 and 180. `name` and `address` label the pin; `url` adds a link beside it. A location cannot be edited after it is sent: `delete_message` is the only change for it.

Parametersurlnamenumberaddressgroup_idlatituderequiredlongituderequiredcontact_idnumber_aliasidempotency_keyquoted_message_id

send_contact_card

Send a contact card

Send a contact card the recipient can save to their phone. Set exactly one recipient: `number`, `group_id`, or `contact_id`. `phones` takes plain E.164 numbers, 1 to 10 of them. `emails`, `organization`, `title` and `address` are optional extra fields on the card; `emails` allows up to 10 as well. A contact card cannot be edited after it is sent: `delete_message` is the only change for it.

Parameterstitleemailsnumberphonesrequiredaddressgroup_idcontact_iddisplay_namerequirednumber_aliasorganizationidempotency_keyquoted_message_id

send_calendar_event

Send a calendar event

Send a calendar event invitation. Set exactly one recipient: `number`, `group_id`, or `contact_id`. `start_at` and `end_at` are ISO 8601 **with an offset**, for example `2026-09-10T14:00:00Z`. A timestamp without one is rejected: the recipient's phone renders the event in their own timezone, so an ambiguous instant would show the wrong hour on their screen. When set, `end_at` must be after `start_at`; an event that ends before (or exactly when) it starts is rejected. `location` pins where it happens (`latitude` and `longitude`, with an optional `name` and `address`). `has_reminder` reminds guests, and `reminder_offset_sec` says how many seconds before the event; `extra_guests_allowed` lets guests bring extra people. To fix a mistake later (a typo, the description, a link), use `edit_calendar_event`: guests keep their answer. When the date, time or place change and guests must accept again, cancel the event with `cancel_calendar_event` and send a new one with this tool. The entry a guest added to their own calendar with the card's "Add to calendar" button never follows an edit or a cancel: it stays on the old date and adding again creates a second entry, so tell guests to delete the old one. To see who said they are coming, use `get_message_responses`.

Parametersnamerequiredend_atnumbergroup_idlocationstart_atrequiredjoin_linkcontact_iddescriptionhas_remindernumber_aliasidempotency_key+3 more

send_cta_button

Send a message with a call-to-action link button

Send an interactive message carrying one link button. Set exactly one recipient: `number`, `group_id`, or `contact_id`. A link button is the only interactive type WhatsApp accepts on this kind of connection. Reply buttons and list pickers are not available and will not become available, so do not try to emulate them. `body` is the text above the button (up to 1024 characters), `button_text` its label (up to 20), `header` and `footer` optional short lines above and below (up to 60 each). A message with a link button cannot be edited after it is sent: `delete_message` is the only change for it.

Parametersbodyrequiredfooterheadernumbergroup_idbutton_urlrequiredcontact_idbutton_textrequirednumber_aliasidempotency_keyquoted_message_id

reply_to_message

Reply to a message in the same conversation

Reply to an EARLIER message, quoting it, without naming a recipient. Use it to bring context into the conversation: when the conversation has moved on and your answer depends on something said a while ago (by the contact, by you or, in a group, by any participant). To answer the message you just received, do not use this tool: send a normal message with `send_text` and let the chat carry the context, as a person would. The recipient is worked out from the original: an inbound message is answered to its sender, an outbound one to whoever received it, and a group message to the group. The message must belong to the inbox named by `number_alias`; a `message_id` from a different number returns not found. Like a person opening the chat, the number first appears online and its unread messages in that conversation are marked as read, then the reply is sent. Opening the conversation this way is automatic and uses no quota of its own.

Parameterstextrequiredmessage_idrequirednumber_aliasidempotency_key

edit_text_message

Edit the text of a message already sent

Replace the text of a text message this number already sent. The recipient sees the new text marked as edited. The message keeps its id and its place in the conversation; only the text changes. Only a text message can be edited here. For the caption of an image, video or document use `edit_media_caption`, and for a calendar event `edit_calendar_event` or `cancel_calendar_event`. A location, contact card, link button, sticker or audio message cannot be edited at all: `delete_message` is the only change for them. Using this tool on any message that is not text answers `invalid_request: message_not_editable`. Only a message sent THROUGH Turbo Notify can be edited. One typed on the phone itself belongs to the same number but cannot be edited here, and neither can a message received from someone else. WhatsApp only accepts a change to a text within 15 minutes of sending it. A later edit is refused up front with `invalid_request: edit_window_expired`; send a new message instead. The edit is accepted at once and the outcome arrives as an event: `message.edited` when it worked, `message.edit.failed` when WhatsApp or the platform could not apply it. Read them with `list_events`. The number first appears online and its unread messages in that conversation are marked as read, then the edit happens. Opening the conversation this way is automatic and uses no quota of its own.

Parameterstextrequiredmessage_idrequirednumber_alias

edit_media_caption

Edit the caption of an image, video or document

Replace the caption of an image, video or document this number already sent. The recipient sees the new caption marked as edited. The file itself stays exactly as it was and the message keeps its id: only the caption changes. There is no way to replace the file; to send a different one, send a new message with `send_media`. Only an image, video or document can be edited this way. Audio and stickers have no caption, and a location, contact card or link button cannot be edited at all: `delete_message` is the only change for them. For the text of a text message use `edit_text_message`. Using this tool on any other message answers `invalid_request: message_not_editable`. Only a message sent THROUGH Turbo Notify can be edited. One typed on the phone itself belongs to the same number but cannot be edited here, and neither can a message received from someone else. WhatsApp only accepts a change to a caption within 15 minutes of sending it. A later edit is refused up front with `invalid_request: edit_window_expired`; send a new message instead. The edit is accepted at once and the outcome arrives as an event: `message.edited` when it worked, `message.edit.failed` when WhatsApp or the platform could not apply it. Read them with `list_events`. The number first appears online and its unread messages in that conversation are marked as read, then the edit happens. Opening the conversation this way is automatic and uses no quota of its own.

Parameterscaptionrequiredmessage_idrequirednumber_alias

edit_calendar_event

Edit a calendar event already sent

Correct a calendar event this number already sent. `name` and `start_at` are always required, plus whatever else the event has: `description`, `end_at`, `join_link`, `location`, `has_reminder` with `reminder_offset_sec`, and `extra_guests_allowed`. This REPLACES the whole event: anything you leave out is removed from it, including its location, reminder and guest setting, so send again every detail you want to keep. The event keeps its place in the chat and the guests keep the answer they already gave. Use an edit to fix a mistake (a typo, the description, a link). When the date, time or place change and the guests must accept again, do not edit: cancel the event with `cancel_calendar_event` and send a new one with `send_calendar_event`, because the old answer does not carry over to a new event. Neither an edit nor a cancel changes the entry a guest already added to their own calendar with the card's "Add to calendar" button. It stays on the old date, and adding again creates a second entry, which only the guest can delete. Only a calendar event can be edited here. Using this tool on any other message answers `invalid_request: message_not_editable`. Only a message sent THROUGH Turbo Notify can be edited. One typed on the phone itself belongs to the same number but cannot be edited here, and neither can a message received from someone else. The 15-minute limit does not apply to a calendar event. The edit is accepted at once and the outcome arrives as an event: `message.edited` when it worked, `message.edit.failed` when WhatsApp or the platform could not apply it. Read them with `list_events`. The number first appears online and its unread messages in that conversation are marked as read, then the edit happens. Opening the conversation this way is automatic and uses no quota of its own.

Parametersnamerequiredend_atlocationstart_atrequiredjoin_linkmessage_idrequireddescriptionhas_remindernumber_aliasreminder_offset_secextra_guests_allowed

cancel_calendar_event

Cancel a calendar event already sent

Cancel a calendar event this number already sent. The event stays in the chat marked as canceled, and WhatsApp tells the guests. Send the same `name` and `start_at` the event has, plus the details you want the canceled card to still show (`description`, `end_at`, `join_link`, `location`, `has_reminder` with `reminder_offset_sec`, `extra_guests_allowed`): the canceled event is shown with what you send here, and anything you leave out disappears from it. Cancel when the date, time or place change and the guests must accept again: cancel the old event here, then send the new one with `send_calendar_event`, because the old answer does not carry over to a new event. To correct a mistake (a typo, the description, a link) use `edit_calendar_event` instead, and the guests keep their answer. A cancel does not change the entry a guest already added to their own calendar with the card's "Add to calendar" button. It stays on the old date, and adding again creates a second entry, which only the guest can delete. Only a calendar event can be canceled. Using this tool on any other message answers `invalid_request: message_not_editable`; to take back a message of another kind, use `delete_message`. Only a message sent THROUGH Turbo Notify can be canceled. One typed on the phone itself belongs to the same number but cannot be canceled here, and neither can a message received from someone else. The 15-minute limit for a text or a caption does not apply to a calendar event. The cancel is accepted at once and the outcome arrives as an event: `message.edited` when it worked, `message.edit.failed` when WhatsApp or the platform could not apply it. Read them with `list_events`. The number first appears online and its unread messages in that conversation are marked as read, then the cancel happens. Opening the conversation this way is automatic and uses no quota of its own.

Parametersnamerequiredend_atlocationstart_atrequiredjoin_linkmessage_idrequireddescriptionhas_remindernumber_aliasreminder_offset_secextra_guests_allowed

delete_message

Delete a message for everyone

Revoke a message for everyone in the conversation. This is WhatsApp's "delete for everyone". It is not reversible, and recipients see a tombstone where the message was, so they can tell something was deleted. Confirm with a human before using it on anything a person has plausibly already read. The number first appears online and its unread messages in that conversation are marked as read, then the message is deleted. Opening the conversation this way is automatic and uses no quota of its own.

Parametersmessage_idrequirednumber_alias

react_to_message

React to a message with an emoji

Attach an emoji reaction to a message, or remove one. Pass a single emoji character, e.g. `👍`. Passing an empty string removes the reaction this number previously left. A new emoji replaces this number's previous reaction rather than adding beside it; to bring the earlier one back, react with it again. Works on messages in either direction: reacting to something a customer sent is often a cheaper acknowledgement than a reply. The number first appears online and its unread messages in that conversation are marked as read, then the reaction is sent. Opening the conversation this way is automatic and uses no quota of its own. Needs a plan that includes reactions. On a plan that does not, this answers `payment_required: feature_not_available`, and the answer will not change by retrying.

Parametersemojirequiredmessage_idrequirednumber_alias

send_typing_indicator

Show the typing indicator in a conversation

Show "typing…" to a recipient while you prepare an answer. Set exactly one recipient: `number`, `group_id`, or `contact_id`. Useful before a reply that takes a few seconds to compose, so the person on the other end sees something is happening. It expires on its own, so clearing it is optional: pass `is_typing=false` only if you decide not to answer after all and want the indicator gone at once. There is no duration argument. WhatsApp decides how long it lives. Starting to type also opens the conversation: the number appears online, its unread messages in that conversation are marked as read, and after a short pause "typing…" appears, so it never shows over unread messages. Stopping (`is_typing=false`) only clears the indicator and reads nothing. Opening the conversation this way is automatic and uses no quota of its own. Needs a plan that includes the typing indicator, and showing it in a group needs one that includes group messaging as well. Without either, this answers `payment_required: feature_not_available`, and the answer will not change by retrying.

Parametersnumbergroup_idis_typingcontact_idnumber_alias

mark_as_read

Clear a conversation's unread backlog

Mark a conversation's unread messages as read, without sending anything. Set exactly one recipient: `number`, `group_id`, or `contact_id`. This is independent of the automatic mark-as-read that already happens when you send, reply, react, edit, delete or start typing (the number also appears online first): use it to clear an unread badge on its own, for example when handing a conversation to a human who has already seen it. No message is sent and no `message.*` event is produced, but the sender sees the messages as read (blue ticks) on WhatsApp. Needs a plan that includes read receipts (BUSINESS and above). Without it this answers `payment_required: feature_not_available`, and the answer will not change by retrying.

Parametersnumbergroup_idcontact_idnumber_alias

list_messages

List messages in an inbox

List messages for one number, newest first, received and sent. Each item says who it is from and to, which number it belongs to, its `direction`, `content_type`, `origin`, a short `preview`, `at`, `status` and `content_expired`. Mentions, transcription and full media detail are only on `get_message`, so fetch the one message you care about rather than paging for detail. To read a group, find its `grp_` id with `list_groups` and pass it as `group_id`. `contact_id` returns what that contact sent or received, which in a group includes what they said there. `direction` keeps only `inbound` or `outbound`. Filters combine: `group_id` with `contact_id` answers what one person said in one group. Ids belong to one number, so use the same `number_alias` you got them from: an id from another number answers not found. For the next page, pass back the `cursor` from the previous response together with the same filters. Content older than the plan's retention window comes back with `preview` empty and `content_expired` true. The message, its sender and its group stay listed for 12 months.

Parameterslimitcursorgroup_iddirectioncontact_idnumber_alias

get_message

Get one message in full

Fetch one message with its full content, parties and mentions. The answer includes the number's stable `number_id` (`num_…`) next to `number_alias`. `at` is when the message actually happened on WhatsApp; `status.at` is when it reached its current delivery status and keeps moving as receipts arrive. They answer different questions and are not interchangeable. Returns not found if the id belongs to a different number. Past the organization's content retention window (1 hour by default, up to 72 hours depending on the plan) the message is still returned, with `content_expired` true and its content empty.

Parametersmessage_idrequirednumber_alias

get_message_status

Get a message's delivery status

Check where a message got to: queued, sent, delivered, read, failed. Status only moves forward. A failed status always carries an error code, and for a few media and reaction problems it also carries a hint naming the field to change; most failures give the code alone. Read the code before retrying, because most failures repeat identically.

Parametersmessage_idrequirednumber_alias

get_message_receipts

Get per-recipient delivery and read receipts

See who received and who read a message. Most useful for group messages, where the single status on the message cannot express that some members read it and others have not. Requires a plan that includes read receipts; on plans without it the call is refused with `feature_not_available`, which no retry will fix.

Parametersmessage_idrequirednumber_alias

get_message_reactions

Get the reactions on a message

List the emoji reactions on a message, and who left each one. Each entry names the reactor and the emoji. Reactions are replaced rather than accumulated per person: someone who reacts twice leaves one entry showing their latest emoji, so this is a current snapshot and not a history. An empty list is the normal answer for most messages, and is not an error.

Parametersmessage_idrequirednumber_alias

get_message_responses

Get the guests' answers to a calendar event

See how the guests answered a calendar event: going, not going or maybe. Only works on a calendar event; any other message answers `invalid_request: message_not_calendar`. The answer has `counts` (how many guests are going, not going or maybe, and how many extra guests those who are going or maybe are bringing) and `responses`, one entry per guest with who they are, their answer, any extra guests they bring and when they answered. A guest who changes their mind leaves one entry showing the latest answer, so this is a current snapshot and not a history. To be told as answers come in, watch for `message.calendar_responded` with `list_events`. An event nobody has answered yet returns zero counts and an empty list, which is the normal answer and not an error.

Parametersmessage_idrequirednumber_alias

get_message_quota

Get a number's message quota for this billing cycle

Check how much of this number's message allowance is left. The quota is **per number**, not per organization: an organization with three numbers has three separate allowances. It resets at `cycle_end`, which is the organization's real billing boundary rather than a rolling window, so `resets_at` is a date you can plan around. Only messages sent through Turbo Notify consume it. Messages someone sends from the phone itself do not.

Parametersnumber_alias

list_contacts

List an inbox's contacts

List the people this number has exchanged messages with. Filter by `phone` or by `name`. They are separate filters, not one search box: `name` matches the contact's name, `phone` matches the number. `source` restricts to how the contact first appeared: `inbound` (they messaged this number), `outbound` (this number messaged them), `mention` (named in a group message) or `self` (the number's own contact card). `contacted_in_cycle=true` narrows the list to exactly the people counting against this number's contact quota this billing cycle: those messaged through Turbo Notify inside the cycle, whether newly or again. Use it to see what the quota gauge is actually counting.

Parametersnamelimitphonecursorsourcenumber_aliascontacted_in_cycle

get_contact

Get one contact

Fetch one contact: name, phone number and activity timestamps. `first_messaged_at` is when a message from this number first reached them on WhatsApp, and `last_interaction_at` is the last message that passed in EITHER direction, so it moves when they write to you as well. A message that failed before reaching WhatsApp moves neither. `refresh` shows the last `refresh_contact` you asked for: when it was requested, when it finished, and its `outcome` (`updated`, `unchanged`, `number_not_connected` or `unavailable`). While `completed_at` is null it is still running. The id must belong to the inbox named by `number_alias`. An id from a different number returns not found even though the person exists.

Parameterscontact_idrequirednumber_alias

get_contact_profile_picture

Get a contact's profile picture

Get a contact's profile picture, or the reason there is none. The answer carries `kind`: `available` with a temporary URL, or `unavailable` with a reason. `no_storage` means the organization has not configured object storage, which is a setup step rather than a fault with this contact. The person may also simply have no picture, or have hidden it in their privacy settings.

Parameterscontact_idrequirednumber_alias

refresh_contact

Refresh a contact's WhatsApp name

Ask WhatsApp for this contact's current name. Only the name is refreshed. The profile picture updates on its own the next time this number exchanges a message with the contact, and this does not change how messages reach them. It answers right away with `requested_at`; the refresh then runs in the background. Call `get_contact` and read `refresh`: when `completed_at` is set, `outcome` says what happened (`updated` means the name changed, `unchanged` means it was already current, `number_not_connected` means the number was offline, `unavailable` means WhatsApp did not answer). This is the one contact tool that is not read-only: it queries WhatsApp over the live connection and spends the organization's per-minute refresh budget, so it needs a key with contacts write access. Do not call it in a loop over a list. Refresh the one contact whose name looks stale.

Parameterscontact_idrequirednumber_alias

get_contact_quota

Get a number's contact quota for this billing cycle

Check how much of this number's contact allowance is left. A contact counts once per billing cycle when this number messages them through Turbo Notify, new **or** returning. Messaging the same person again in a later cycle counts again. Like the message quota, this is per number rather than per organization, and it resets at `cycle_end`.

Parametersnumber_alias

list_groups

List the groups a number belongs to

List the WhatsApp groups this number is a member of. The list is built when the number connects: every group it belongs to is picked up then, whether or not anything has been said in it. A group joined since then is picked up from the join itself, without waiting for anyone to speak in it. `name` filters by group name. `include_left=true` also returns groups this number has left, which are otherwise hidden.

Parametersnamelimitcursorinclude_leftnumber_alias

get_group

Get one group

Fetch a group: its name, description, picture and settings. Returns how many participants it has, not who they are. Membership is deliberately not readable: it arrives as it changes, on the `group.participants_added` / `_removed` / `_promoted` / `_demoted` events, which `list_events` can read.

Parametersgroup_idrequirednumber_alias

get_group_picture

Get a group's picture

Get a group's picture, or the reason there is none. Same shape as a contact's picture. The answer carries `kind`: `available` with a temporary URL, or `unavailable` with a reason. `no_storage` means the organization has not configured object storage.

Parametersgroup_idrequirednumber_alias

list_numbers

List the organization's WhatsApp numbers

List every WhatsApp number this organization has, with its status. Start here when you do not know which aliases exist. The main number is always `main`; extras carry the alias the customer chose, and that alias is what every other tool's `number_alias` expects. Each number also carries `id` (`num_…`), which never changes, even when the alias is renamed. Keep it if you need to recognise a number later: events carry the same value as `number_id`, and the other tools accept either one as `number_alias`. To find the numbers that need attention, pass `status`, for example `disconnected`. Only `connected` numbers can send. `status.at` is when the number entered its current state, which is how you tell a fresh disconnection from one that has been down for hours.

Parametersstatus

get_number

Get one number and its connection status

Check one number: its phone number, status, and when that status began. `id` (`num_…`) is the number's permanent identifier. The alias can be renamed, the `id` cannot. Either one works as `number_alias`. `connected` means it can send right now. `connecting` means a connection was just requested and is being set up; check again in a few seconds. `reconnecting` means the number lost its connection and Turbo Notify is bringing it back by itself: there is nothing to do but wait and check again. Until it is `connected` again, sends are refused with `number_not_connected`. `disconnected` means the link to the phone still exists but the number is offline, so `connect_number` brings it back. `inactive` is the same for a number that was linked and has not been brought online yet: `connect_number` brings it online too. `pending` means the number was never linked to a phone, and `no_main_number` (what a brand-new account gets for `main`, and `list_numbers` is empty) means there is no number at all yet: either way a person has to link it from the Numbers page of the dashboard, so tell them and do not retry the send meanwhile. `revoked` means the phone unlinked the device, or another connection took it over. `failed` means a pairing did not complete, and `reason` says why (for example `pairing_timeout` when the code or QR code was not used in time, `paired_phone_mismatch` when another WhatsApp used it). Either one has to be paired again by a person holding that phone: from the dashboard (main or extra number) or, for an extra number, through the API's pairing endpoint, by code or by QR code. No tool here pairs a number: it needs a person with the phone. `pairing` means a person is pairing the number right now. The code or QR code they use is not part of this answer: it belongs to that person. Check again later; the status moves to `connected`, or to `failed` with a `reason`.

Parametersnumber_alias

connect_number

Bring a number back online

Reconnect a number that is currently offline. Uses the credentials already stored for the number, so no QR code and no human are involved. Use it when `get_number` reports `disconnected` and you want to send. Returns immediately, before the connection is established. The answer has the number's `alias`, `status` set to `connecting` and `at`, the moment the request was accepted. The number reaches `connected` a few seconds later: poll `get_number`, which stays the source of the current status and of its own `at`, rather than assuming it worked. It works the same on every number and never charges. On an extra number scheduled for removal it does not change the removal: the number keeps working until its date. Cancelling a removal is not available to agents. This cannot rescue a `revoked` number: that one was unlinked from the phone and needs a person to pair it again. Calling it again changes nothing further. A number that is already `connected` or `connecting` answers with an `already_connected` or `already_connecting` error, which means the goal is met, not that the call failed.

Parametersnumber_alias

disconnect_number

Take a number offline without unlinking it

Take a number offline deliberately, keeping its link to the phone. The stored credentials survive, so `connect_number` brings it straight back with no pairing. Nothing is deleted. Disconnecting does not stop billing: an extra number is billed every cycle until it is removed. Returns immediately, before the number is offline. The answer has the number's `alias`, `status` set to `disconnecting` and `at`, the moment the request was accepted. `disconnecting` is not a status that `get_number` reports: it keeps showing the previous status and its own `at` until the number is offline. While disconnected the number cannot send or receive, and messages addressed to it are refused rather than queued. Deliberate: a message held for an unknown period and delivered later is worse than one that failed clearly. Calling it again changes nothing further. A number that is already offline answers with an `already_disconnected` error, which means the goal is met, not that the call failed.

Parametersnumber_alias

get_usage

Get the organization's usage across all numbers

Get message and contact usage for the whole organization. The top-level totals are the **sum across every live number**: an organization with two numbers on a 50,000-message plan reports a quota of 100,000, not 50,000. The `numbers[]` breakdown shows each number separately, which is what you need to find the one that is close to its own ceiling, so a healthy-looking total can hide a number at 99%. For one number, `get_message_quota` and `get_contact_quota` are more direct.

Takes no parameters.

list_events

Poll the organization's event log

Poll everything that happened: messages, contacts, groups, numbers, quotas. This is how an agent learns about inbound messages without hosting a webhook endpoint. **Events come back newest first, so polling for new ones uses `ending_before`, not `starting_after`.** Typical loop: call with no cursor and remember the id of the FIRST event in the page (the newest one); next time, pass that id as `ending_before` to get only what has happened since. An empty page means you are caught up. `starting_after` does the opposite: it walks BACKWARD into history, which is what you want for reading a backlog, and never what you want for noticing something new. Passing the last id you handled to `starting_after` in a loop returns steadily older events and never reaches the present. `request_id` returns everything that came of one earlier call. A write tool's result carries `request_id` when the platform issued one; keep it and you can ask later what became of that specific send. `number_id` restricts to one number and keeps matching its events after the number is renamed. `number_alias` restricts to one inbox by the alias it had when the event happened, so a rename hides the older events; the two can be combined. Either one excludes `storage.*` and `media.unavailable` events, which belong to the organization rather than to any one number. Most events carry a `number_alias` telling you which inbox they belong to, and `number_id` (`num_…`), the same number's permanent identifier. The alias is the one at the time of the event and can be renamed later; the `number_id` cannot. You need one of them: message, contact and group ids are unique but not self-addressing, so an id alone does not tell you which number's tools to use with it, and both work as `number_alias` in the other tools. Read the fields defensively rather than assuming they are there: `storage.*` and `media.unavailable` belong to the organization as a whole and have no single number to name, so on those the keys are a

Parameterslimitnumber_idevent_typerequest_idcreated_gtecreated_lteevent_typesnumber_aliasending_beforestarting_after

Version history

1
  1. v0.2.1LatestNov 6, 2026

Score

100% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts

Other Turbonotify integrations