Blueticks

Sending messages

Send text, media, or poll messages immediately or schedule them for later via /v1/scheduled-messages.

This guide assumes you have an API key (Authentication) and an SDK client set up (Quickstart).

There are two ways to send a WhatsApp message. Pick based on whether you want a tracked record:

EndpointWhen to use itTracked?
POST /v1/scheduled-messagesThe default for almost everything. Send right now or schedule for later.Yes — returns a record you can list, poll for status, and edit.
POST /v1/messages/{chat_id}A lightweight, fire-and-forget send into one chat.No — dispatched directly, no record kept.

Despite the name, POST /v1/scheduled-messages is the main "send a message" endpoint — not just for scheduling. Omit sendAt and the message goes out immediately; add sendAt and it's scheduled for later. This guide uses it for every example below.

The body is a flat object — set type to one of text, media, or poll; type selects which fields are required. The optional fields sendAt (schedule) and replyTo (quote a message) work on every variant.

Send a text message

Omit sendAt to send it right now:

curl -X POST 'https://api.blueticks.co/v1/scheduled-messages' \  -H 'Authorization: Bearer BLUETICKS_API_KEY' \  -H 'Content-Type: application/json' \  -d '{  "type": "text",  "to": "string"}'
import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.scheduled_messages.create(    # request body fields…,)
import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.scheduledMessages.create({ /* … */ });
use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->scheduled_messages->create(/* opts */);
require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.scheduled_messages.create(  # request body fields…,)
import (	"context"	blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.ScheduledMessages.Create(context.Background(), params)
{  "type": "text",  "to": "string"}

Response

{  "success": true,  "data": {    "id": "string",    "key": "string",    "to": "string",    "type": "text",    "text": "string",    "mediaUrl": "string",    "mediaKind": "image",    "pollQuestion": "string",    "status": "pending",    "sendAt": "2026-01-01T00:00:00Z",    "createdAt": "2026-01-01T00:00:00Z",    "confirmedAt": "2026-01-01T00:00:00Z",    "receivedAt": "2026-01-01T00:00:00Z",    "readAt": "2026-01-01T00:00:00Z",    "playedAt": "2026-01-01T00:00:00Z",    "failedAt": "2026-01-01T00:00:00Z",    "failureReason": "string"  }}

Send to a group or channel

The to field accepts a phone number in international format OR a WhatsApp JID. JIDs come in three flavors:

  • <digits>@c.us — individual chat (digits-only equivalent of an international-format phone number with + stripped).
  • <id>@g.us — group chat (e.g. 120363427920657250@g.us). Find a group's id with GET /v1/groups or by inspecting the URL in WhatsApp Web.
  • <id>@newsletter — channel (e.g. 12345@newsletter).

The send call is identical — just swap the to:

import blueticks
bt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")

# Send to a group
msg = bt.scheduled_messages.create(
    to="120363427920657250@g.us",
    type="text",
    text="Team standup in 10 minutes!",
)

# Send to a channel
msg = bt.scheduled_messages.create(
    to="12345@newsletter",
    type="text",
    text="New announcement for all subscribers.",
)
import { Blueticks } from 'blueticks';
const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });

// Send to a group
const groupMsg = await bt.scheduledMessages.create({
  to: '120363427920657250@g.us',
  type: 'text',
  text: 'Team standup in 10 minutes!',
});

// Send to a channel
const channelMsg = await bt.scheduledMessages.create({
  to: '12345@newsletter',
  type: 'text',
  text: 'New announcement for all subscribers.',
});
use Blueticks\Blueticks;
$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);

// Send to a group
$groupMsg = $bt->scheduled_messages->create([
    'to'   => '120363427920657250@g.us',
    'type' => 'text',
    'text' => 'Team standup in 10 minutes!',
]);

// Send to a channel
$channelMsg = $bt->scheduled_messages->create([
    'to'   => '12345@newsletter',
    'type' => 'text',
    'text' => 'New announcement for all subscribers.',
]);
require "blueticks"
client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")

# Send to a group
client.scheduled_messages.create(
  to: "120363427920657250@g.us",
  type: "text",
  text: "Team standup in 10 minutes!",
)

# Send to a channel
client.scheduled_messages.create(
  to: "12345@newsletter",
  type: "text",
  text: "New announcement for all subscribers.",
)
import (
	"context"

	blueticks "github.com/serenix-com/blueticks-go"
)

c, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))

// Send to a group
c.ScheduledMessages.Create(context.Background(), blueticks.CreateScheduledMessageParams{
	To:   "120363427920657250@g.us",
	Type: "text",
	Text: "Team standup in 10 minutes!",
})

// Send to a channel
c.ScheduledMessages.Create(context.Background(), blueticks.CreateScheduledMessageParams{
	To:   "12345@newsletter",
	Type: "text",
	Text: "New announcement for all subscribers.",
})
# Send to a group
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
  -H "Authorization: Bearer BLUETICKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "120363427920657250@g.us",
    "type": "text",
    "text": "Team standup in 10 minutes!"
  }'

# Send to a channel
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
  -H "Authorization: Bearer BLUETICKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "12345@newsletter",
    "type": "text",
    "text": "New announcement for all subscribers."
  }'

The validator accepts a phone number in international format (+<digits>) or any of the three JID suffixes (@c.us, @g.us, @newsletter).

Send media

type: "media" takes the file as mediaUrl (an HTTPS URL) or mediaBase64 (the raw bytes base64-encoded, or a data: URL); mediaUrl wins if both are present. The optional mediaKind (image · video · audio · document · sticker · voice · gif) is auto-detected from the URL or the Content-Type of the fetched asset when omitted. text doubles as the caption under image/video in WhatsApp; mediaFilename is shown for documents.

import blueticks
bt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")

msg = bt.scheduled_messages.create(
    to="+972501234567",
    type="media",
    media_url="https://cdn.example.com/receipt.pdf",
    media_kind="document",
    media_filename="receipt-A42.pdf",
    text="Order #A-42",  # caption
)
print(msg.id, msg.status)
import { Blueticks } from 'blueticks';
const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });

const msg = await bt.scheduledMessages.create({
  to: '+972501234567',
  type: 'media',
  mediaUrl: 'https://cdn.example.com/receipt.pdf',
  mediaKind: 'document',
  mediaFilename: 'receipt-A42.pdf',
  text: 'Order #A-42', // caption
});
use Blueticks\Blueticks;
$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);

$msg = $bt->scheduled_messages->create([
    'to'            => '+972501234567',
    'type'          => 'media',
    'mediaUrl'      => 'https://cdn.example.com/receipt.pdf',
    'mediaKind'     => 'document',
    'mediaFilename' => 'receipt-A42.pdf',
    'text'          => 'Order #A-42', // caption
]);

Ruby and Go nest the file details under a media object (caption lives there):

require "blueticks"
client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")

msg = client.scheduled_messages.create(
  to: "+972501234567",
  type: "media",
  media: {
    "url" => "https://cdn.example.com/receipt.pdf",
    "kind" => "document",
    "filename" => "receipt-A42.pdf",
    "caption" => "Order #A-42",
  },
)
puts msg.id, msg.status
import (
	"context"

	blueticks "github.com/serenix-com/blueticks-go"
)

c, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))

msg, _ := c.ScheduledMessages.Create(context.Background(), blueticks.CreateScheduledMessageParams{
	To:   "+972501234567",
	Type: "media",
	Media: &blueticks.SendMediaObject{
		URL:      "https://cdn.example.com/receipt.pdf",
		Kind:     "document",
		Filename: "receipt-A42.pdf",
		Caption:  "Order #A-42",
	},
})
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
  -H "Authorization: Bearer BLUETICKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+972501234567",
    "type": "media",
    "mediaUrl": "https://cdn.example.com/receipt.pdf",
    "mediaKind": "document",
    "mediaFilename": "receipt-A42.pdf",
    "text": "Order #A-42"
  }'

Voice notes are media with mediaKind: "voice":

{ "to": "+972...", "type": "media", "mediaUrl": "https://.../note.ogg", "mediaKind": "voice" }

mediaUrl must be https:// and resolve to a non-private host — we fetch and forward it on your behalf. If the file has no public URL, send the bytes inline as mediaBase64 instead.

Send a poll

pollOptions must contain 2–12 entries. pollAllowMultiple lets recipients pick more than one (default false).

import blueticks
bt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")

msg = bt.scheduled_messages.create(
    to="+972501234567",
    type="poll",
    poll_question="Pizza tonight?",
    poll_options=["Yes", "No", "Maybe"],
    poll_allow_multiple=False,
)
print(msg.id, msg.status)
import { Blueticks } from 'blueticks';
const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });

const msg = await bt.scheduledMessages.create({
  to: '+972501234567',
  type: 'poll',
  pollQuestion: 'Pizza tonight?',
  pollOptions: ['Yes', 'No', 'Maybe'],
  pollAllowMultiple: false,
});
use Blueticks\Blueticks;
$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);

$msg = $bt->scheduled_messages->create([
    'to'                => '+972501234567',
    'type'              => 'poll',
    'pollQuestion'      => 'Pizza tonight?',
    'pollOptions'       => ['Yes', 'No', 'Maybe'],
    'pollAllowMultiple' => false,
]);
require "blueticks"
client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")

msg = client.scheduled_messages.create(
  to: "+972501234567",
  type: "poll",
  poll: {
    "question" => "Pizza tonight?",
    "options" => ["Yes", "No", "Maybe"],
    "allow_multiple" => false,
  },
)
puts msg.id, msg.status
import (
	"context"

	blueticks "github.com/serenix-com/blueticks-go"
)

c, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))

msg, _ := c.ScheduledMessages.Create(context.Background(), blueticks.CreateScheduledMessageParams{
	To:   "+972501234567",
	Type: "poll",
	Poll: &blueticks.SendPollObject{
		Question:      "Pizza tonight?",
		Options:       []string{"Yes", "No", "Maybe"},
		AllowMultiple: false,
	},
})
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
  -H "Authorization: Bearer BLUETICKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+972501234567",
    "type": "poll",
    "pollQuestion": "Pizza tonight?",
    "pollOptions": ["Yes", "No", "Maybe"],
    "pollAllowMultiple": false
  }'

Schedule for later

sendAt (ISO 8601 with offset, ≥ 10 s in the future, ≤ 365 d out) works on every type. The response status is pending — the message waits until the sendAt time before dispatching.

{ "to": "+972...", "type": "text", "text": "Reminder", "sendAt": "2026-12-01T09:00:00Z" }

Quote-reply to a prior message

Set replyTo to the wire key of a message in the same chat (returned in MessageResponse.key once the original has been sent). Works with text, media, and poll.

{ "to": "+972...", "type": "text", "text": "Got it 👍", "replyTo": "false_972...@c.us_3EB0..." }

React to a message

Reactions are a separate endpoint — POST /v1/messages/reactions/{chat_id}/{key} with body { "emoji": "❤️" }. Pass an empty string to remove a reaction. Use the key from MessageResponse, not the id.

import blueticks
bt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")

bt.chats.react(
    chat_id="972501234567@c.us",
    key="false_972501234567@c.us_3EB0ABCDEF",
    emoji="❤️",
)
import { Blueticks } from 'blueticks';
const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });

await bt.messages.react(
  '972501234567@c.us',
  'false_972501234567@c.us_3EB0ABCDEF',
  { emoji: '❤️' },
);
use Blueticks\Blueticks;
$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);

$bt->chats->react(
    '972501234567@c.us',
    'false_972501234567@c.us_3EB0ABCDEF',
    '❤️',
);
require "blueticks"
client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")

client.chats.react(
  "972501234567@c.us",
  "false_972501234567@c.us_3EB0ABCDEF",
  emoji: "❤️",
)
import (
	"context"

	blueticks "github.com/serenix-com/blueticks-go"
)

c, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))

c.Chats.React(
	context.Background(),
	"972501234567@c.us",
	"false_972501234567@c.us_3EB0ABCDEF",
	"❤️",
)
curl -X POST "https://api.blueticks.co/v1/messages/reactions/972501234567@c.us/false_972501234567@c.us_3EB0ABCDEF" \
  -H "Authorization: Bearer BLUETICKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emoji":"❤️"}'

Idempotency

Safe retries via the Idempotency-Key header. Same key + same body within 24 h returns the original response (as a 200 replay instead of 201). Different body with the same key returns 409.

import blueticks
bt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")

msg = bt.scheduled_messages.create(
    to="+972501234567",
    type="text",
    text="Your order is confirmed.",
    idempotency_key="order-42-reminder",
)
import { Blueticks } from 'blueticks';
const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });

const msg = await bt.scheduledMessages.create({
  to: '+972501234567',
  type: 'text',
  text: 'Your order is confirmed.',
  idempotencyKey: 'order-42-reminder',
});
use Blueticks\Blueticks;
$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);

$msg = $bt->scheduled_messages->create([
    'to'              => '+972501234567',
    'type'            => 'text',
    'text'            => 'Your order is confirmed.',
    'idempotency_key' => 'order-42-reminder',
]);
require "blueticks"
client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")

msg = client.scheduled_messages.create(
  to: "+972501234567",
  type: "text",
  text: "Your order is confirmed.",
  idempotency_key: "order-42-reminder",
)
import (
	"context"

	blueticks "github.com/serenix-com/blueticks-go"
)

c, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))

msg, _ := c.ScheduledMessages.Create(context.Background(), blueticks.CreateScheduledMessageParams{
	To:             "+972501234567",
	Type:           "text",
	Text:           "Your order is confirmed.",
	IdempotencyKey: "order-42-reminder",
})
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
  -H "Authorization: Bearer BLUETICKS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-42-reminder" \
  -d '{
    "to": "+972501234567",
    "type": "text",
    "text": "Your order is confirmed."
  }'

Status lifecycle

pending ──▶ confirmed ──▶ received ──▶ read ──▶ played
                     └──▶ failed
  • pending — accepted by the API; waiting to dispatch (either immediately or at sendAt).
  • confirmed — WhatsApp accepted the message (wire key received). confirmedAt is populated.
  • received — double grey tick: the recipient's device has it. receivedAt is populated.
  • read — double blue tick: the recipient opened the message. readAt is populated.
  • played — voice note played by the recipient. playedAt is populated.
  • failed — terminal. See the failureReason field on the message for the cause.

Poll the current state with GET /v1/scheduled-messages/{id}. Read receipts (readAt populated, message.read webhook fires) are delivered via webhooks — see the Webhooks guide.

Manage scheduled messages

List all your messages (pending, in-flight, or delivered) with GET /v1/scheduled-messages:

curl -X GET 'https://api.blueticks.co/v1/scheduled-messages' \  -H 'Authorization: Bearer BLUETICKS_API_KEY'
import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.scheduled_messages.list()
import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.scheduledMessages.list();
use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->scheduled_messages->list();
require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.scheduled_messages.list()
import (	"context"	blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.ScheduledMessages.List(context.Background())

Fetch one by id to read its current status:

curl -X GET 'https://api.blueticks.co/v1/scheduled-messages/string' \  -H 'Authorization: Bearer BLUETICKS_API_KEY'
import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.scheduled_messages.retrieve(    "id_01H7...",)
import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.scheduledMessages.retrieve('id_01H7...');
use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->scheduled_messages->retrieve('id_01H7...');
require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.scheduled_messages.retrieve(  "id_01H7...",)
import (	"context"	blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.ScheduledMessages.Retrieve(context.Background(), "id_01H7...")

Before a message dispatches (while still pending) you can edit it with PATCH /v1/scheduled-messages/{id} — reschedule (sendAt), rewrite the body (text), or swap the attachment (mediaUrl, mediaCaption). At least one field is required; once the message has dispatched, the edit returns 400:

curl -X PATCH 'https://api.blueticks.co/v1/scheduled-messages/string' \  -H 'Authorization: Bearer BLUETICKS_API_KEY' \  -H 'Content-Type: application/json' \  -d '{  "text": "string",  "mediaUrl": "https://example.com",  "mediaCaption": "string",  "sendAt": "2026-01-01T00:00:00Z"}'
import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.scheduled_messages.update(    "id_01H7...",    # request body fields…,)
import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.scheduledMessages.update('id_01H7...', { /* … */ });
use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->scheduled_messages->update('id_01H7...', /* opts */);
require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.scheduled_messages.update(  "id_01H7...",  # request body fields…,)
import (	"context"	blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.ScheduledMessages.Update(context.Background(), "id_01H7...", params)
{  "text": "string",  "mediaUrl": "https://example.com",  "mediaCaption": "string",  "sendAt": "2026-01-01T00:00:00Z"}

Immediate send into an open chat

The second send path from the top of this guide. POST /v1/messages/{chat_id} posts straight into one conversation and returns the WhatsApp wire key. It's fire-and-forget — no record is created, so there's nothing to list, poll, or edit afterwards. Reach for it only when you don't need tracking; otherwise use POST /v1/scheduled-messages (above). The body is the same flat text / media / poll shape, minus to (it's in the URL) and sendAt. It also accepts multipart/form-data with the file in a mediaFile part, for media sends without a public URL:

curl -X POST 'https://api.blueticks.co/v1/messages/string' \  -H 'Authorization: Bearer BLUETICKS_API_KEY' \  -H 'Content-Type: application/json' \  -d '{  "type": "text"}'
import blueticksbt = blueticks.Blueticks(api_key="BLUETICKS_API_KEY")result = bt.chats.send_message(    "chat_id_01H7...",    # request body fields…,)
import { Blueticks } from 'blueticks';const bt = new Blueticks({ apiKey: 'BLUETICKS_API_KEY' });const result = await bt.messages.send('chat_id_01H7...', { /* … */ });
use Blueticks\Blueticks;$bt = new Blueticks(['apiKey' => 'BLUETICKS_API_KEY']);$result = $bt->chats->sendMessage('chat_id_01H7...', /* opts */);
require "blueticks"client = Blueticks::Client.new(api_key: "BLUETICKS_API_KEY")result = client.chats.send_message(  "chat_id_01H7...",  # request body fields…,)
import (	"context"	blueticks "github.com/serenix-com/blueticks-go")client, _ := blueticks.NewClient(blueticks.WithAPIKey("BLUETICKS_API_KEY"))result, _ := client.Chats.SendMessage(context.Background(), "chat_id_01H7...", params)
{  "type": "text"}

Response

A successful send returns 200 OK; the wire key is under data.key:

{
  "success": true,
  "data": {
    "id": null,
    "key": "false_972501234567@c.us_3EB0ABCDEF",
    "to": "972501234567@c.us",
    "type": "text",
    "text": "Got it 👍",
    "mediaUrl": null,
    "mediaKind": null,
    "pollQuestion": null,
    "status": "confirmed",
    "sendAt": null,
    "createdAt": "2026-06-12T09:00:00.000Z",
    "confirmedAt": "2026-06-12T09:00:01.000Z",
    "receivedAt": null,
    "readAt": null,
    "playedAt": null,
    "failedAt": null,
    "failureReason": null
  }
}

Beyond sending: read, groups & channels

Sending is one half of the API. The flows below each have a full, interactive reference page — here are the common entry points:

Not yet supported

These message types are on the roadmap but not yet exposed in the v1 API. Use the MCP server or watch the changelog for ship dates:

  • Location — share a coordinate with optional name/address.
  • Contacts (vcard) — share a contact card.
  • Buttons / list — interactive replies and selectable options.