Tools and code samples
Postman
Download the Threadwire Postman collection and import it (Import → drop the file, or paste that link). Open the collection's Variables and fill in api_key (your API key) and instance_id (your instance's ID); phone is who test messages go to, and ids such as message_id come from earlier answers. Every request uses them, so nothing else needs setting.
- Every endpoint is there, grouped as in the reference, with a ready body for each request that takes one.
- Beside Send a message is a ready request for each kind of message: an image, a PDF, a voice note, a file as base64, a link with or without its preview, a location, a contact card, a poll, a sticker, a quoted reply, a scheduled message, a group. They use sample files of ours, so each sends as imported.
- Requests that delete or unlink something (removing an instance, unlinking WhatsApp, deleting a message or a label) use their own variables ending in
_to_remove, left empty, so running the whole collection cannot remove anything by accident.
Insomnia and other tools: import https://threadwire.net/docs/api.json, the OpenAPI description of the reference, and set Bearer Token authorization to your API key.
Laravel
Our package tritech/threadwire-laravel sends through the API (texts, files, places, contact cards, polls), throws our refusals with their reason, and receives your webhooks as Laravel events with the signature checked. From 2.0 it runs WhatsApp OTP (verifyByLink(), sendOtpCode(), checkOtp(); 1.x called these by their old names, which no longer work), polls for events (events()), and starts and follows broadcasts (createBroadcast()).
composer require tritech/threadwire-laravel
Source and examples: github.com/khaledtarek54/threadwire-laravel.
Send a message in your language
Each sends a text; add the other fields from Sending messages the same way.
curl https://threadwire.net/api/v1/messages \
-H "Authorization: Bearer $THREADWIRE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-shipped" \
-d '{"instance_id": "YOUR_INSTANCE_ID", "to": "201012345678", "text": "Hi Sara, your order 1042 is on its way."}'
// PHP
$request = curl_init('https://threadwire.net/api/v1/messages');
curl_setopt_array($request, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer '.getenv('THREADWIRE_KEY'), 'Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode(['instance_id' => 'YOUR_INSTANCE_ID', 'to' => '201012345678', 'text' => 'Hi Sara, your order 1042 is on its way.']),
]);
$answer = json_decode(curl_exec($request), true);
if (curl_getinfo($request, CURLINFO_HTTP_CODE) >= 400) {
throw new RuntimeException($answer['message'] ?? 'Threadwire refused the request');
}
$message = $answer['data'];
// Node.js 18 or later, in an ES module (.mjs, or "type": "module")
const response = await fetch('https://threadwire.net/api/v1/messages', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.THREADWIRE_KEY}`, 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ instance_id: 'YOUR_INSTANCE_ID', to: '201012345678', text: 'Hi Sara, your order 1042 is on its way.' }),
});
if (!response.ok) throw new Error(await response.text());
const { data: message } = await response.json();
# Python, with requests
import os, requests
response = requests.post(
'https://threadwire.net/api/v1/messages',
headers={'Authorization': f"Bearer {os.environ['THREADWIRE_KEY']}", 'Accept': 'application/json'},
json={'instance_id': 'YOUR_INSTANCE_ID', 'to': '201012345678', 'text': 'Hi Sara, your order 1042 is on its way.'},
)
response.raise_for_status()
message = response.json()['data']
// C# (.NET 8), with HttpClient
using System.Net.Http.Headers;
using System.Net.Http.Json;
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("THREADWIRE_KEY"));
var response = await http.PostAsJsonAsync("https://threadwire.net/api/v1/messages",
new { instance_id = "YOUR_INSTANCE_ID", to = "201012345678", text = "Hi Sara, your order 1042 is on its way." });
response.EnsureSuccessStatusCode();
// Java 11+, with java.net.http
var request = HttpRequest.newBuilder(URI.create("https://threadwire.net/api/v1/messages"))
.header("Authorization", "Bearer " + System.getenv("THREADWIRE_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"instance_id\":\"YOUR_INSTANCE_ID\",\"to\":\"201012345678\",\"text\":\"Hi Sara, your order 1042 is on its way.\"}"))
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
// Go, with net/http
body := strings.NewReader(`{"instance_id":"YOUR_INSTANCE_ID","to":"201012345678","text":"Hi Sara, your order 1042 is on its way."}`)
req, _ := http.NewRequest("POST", "https://threadwire.net/api/v1/messages", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("THREADWIRE_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
# Ruby, with net/http
require 'net/http'
require 'json'
uri = URI('https://threadwire.net/api/v1/messages')
Net::HTTP.post(uri, { instance_id: 'YOUR_INSTANCE_ID', to: '201012345678', text: 'Hi Sara, your order 1042 is on its way.' }.to_json,
'Authorization' => "Bearer #{ENV['THREADWIRE_KEY']}", 'Content-Type' => 'application/json')
To check the webhooks we send you, see the samples in Webhooks and events.
n8n
In n8n, choose Import from URL and paste one of these; each explains its setup in a note beside it. They use n8n's own Webhook and HTTP Request nodes, so nothing needs installing.
- Answer WhatsApp messages: runs your own steps (look up an order, ask an AI) and replies.
- Save incoming messages to Google Sheets.
- Send a WhatsApp message from any workflow.
Make
There is no Threadwire app in Make yet. Make's own modules do the same today:
- To receive, add a Webhooks → Custom webhook module and set its address as your webhook URL (Developers → Webhooks in your panel, admins only). Send your instance one message so Make learns the fields (
data.phone,data.body…), then add a filter after the module:typeequalsmessage.received. - To send, add HTTP → Make a request:
POST https://threadwire.net/api/v1/messages, a headerAuthorization: Bearer YOUR_API_KEY, and a JSON body withinstance_id,toandtext.
For both n8n and Make:
- Leave your events as they are. Events are one list for your whole account (Developers → Webhooks → Events), and the same list decides what
GET /v1/eventskeeps. Un-ticking the others to get onlymessage.receivedswitches them off for every system on your account: delivery updates, OTP results, broadcasts. Filter bytypeinside the tool instead, as the two receiving workflows above do. - Lock the address. n8n and Make cannot check our signature without code. So set an authentication header on your Webhooks page ("Authentication header": a name and a secret value of yours), and have the tool require the same header. In n8n: the Webhook node's Authentication → Header Auth, with that name and value.
- One URL per account. It goes to one of these tools (or the WooCommerce plugin) at a time; an instance can have its own URL to split them.
- No public address? Poll instead: an n8n Schedule Trigger every minute, then HTTP Request to
GET /v1/eventswithafter(see polling). - Replies to people who wrote are never limited as first messages; a message to someone who never wrote is, so let people write first.
WooCommerce
The Threadwire for WooCommerce plugin sends order updates on WhatsApp to buyers who ask for them. After checkout, the thank-you page and the order email show "Get order updates on WhatsApp"; the button opens WhatsApp with a message about the order already typed. Once the buyer sends it, the order is linked to their chat, and each status you choose (processing, completed, cancelled…) sends them a message you write. Every update is a reply in a chat the buyer started, so your instance stays safe: it goes only to the phone that asked, from the instance it wrote to, within 30 days of their message. The first phone to send an order's reference keeps it.
To set it up:
- Install the zip in WordPress (Plugins → Add New → Upload).
- Make an API key that may send (Developers → API keys, admins only).
- Set your webhook URL (Developers → Webhooks) to the address the plugin shows in WooCommerce → Settings → WhatsApp. Or set it as that instance's own webhook URL on its page, to keep your account's URL for other systems.
- Leave your events as they are: the plugin reads only
message.receivedand ignores the rest. The events are one list for your whole account, so un-ticking the others would switch them off for every other system too. - Fill in the key, the Instance ID (on the instance's page) and the signing secret there.
AI agents (MCP)
Threadwire is also a Model Context Protocol server, so an AI agent can read your chats and answer through your instances. Point an MCP client that takes a Bearer header (Claude Code, Cursor, n8n's MCP Client tool, the MCP SDKs) at https://threadwire.net/mcp with your API key as the Bearer token. For Claude Code:
claude mcp add --transport http threadwire https://threadwire.net/mcp --header "Authorization: Bearer YOUR_KEY"
- Tools:
list_numbers,list_chats,get_messages,get_events(what happened since it last looked: passafter),send_message(give it anidempotency_key, so a retry never sends twice),check_number,list_groups,start_otp,get_otpandcheck_otp(WhatsApp OTP), andstart_broadcast,get_broadcastandcontrol_broadcast(Broadcasts). - Pages: lists come a page at a time, as in the API: an answer says
has_more, and the agent asks again withpage(next_page),cursor(next_cursor, for older messages) oroffset(next_offset, for groups). - Same rules: each tool is the API behind it: a key that may only read cannot send, and every message is paced and protected like any other. Each request counts as an API request against a plan's monthly allowance, opening the session included.