Threadwire by TriTech

← API reference

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.

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: type equals message.received.
  • To send, add HTTP → Make a request: POST https://threadwire.net/api/v1/messages, a header Authorization: Bearer YOUR_API_KEY, and a JSON body with instance_id, to and text.

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/events keeps. Un-ticking the others to get only message.received switches them off for every system on your account: delivery updates, OTP results, broadcasts. Filter by type inside 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/events with after (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:

  1. Install the zip in WordPress (Plugins → Add New → Upload).
  2. Make an API key that may send (Developers → API keys, admins only).
  3. 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.
  4. Leave your events as they are: the plugin reads only message.received and 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.
  5. 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: pass after), send_message (give it an idempotency_key, so a retry never sends twice), check_number, list_groups, start_otp, get_otp and check_otp (WhatsApp OTP), and start_broadcast, get_broadcast and control_broadcast (Broadcasts).
  • Pages: lists come a page at a time, as in the API: an answer says has_more, and the agent asks again with page (next_page), cursor (next_cursor, for older messages) or offset (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.