If you have ever wanted to fire a WhatsApp message straight from your own backend — a payment reminder the night before it's due, an order alert the moment a webhook lands, a follow-up three days after signup — you have probably run into the same wall I did. Most "WhatsApp APIs" send a message now. Very few let you hand them a timestamp and say "deliver this Tuesday at 9am." And almost none of them let you do it from the number you already use, without applying to Meta first.
This guide walks through both halves of that problem in Python and Node: sending a WhatsApp message from code, and the rarer trick — scheduling one for later via a single REST call. The examples below use the Blueticks API, which I picked specifically because the scheduling endpoint and the own-number transport are the two things that are hard to find elsewhere. I'll be honest about the trade-offs too, because there are real ones.
When you want to send or schedule WhatsApp from code
A few patterns show up over and over once you can talk to WhatsApp programmatically:
- Reminders. Appointment confirmations, rent-due nudges, "your trial ends tomorrow." These are almost always future-dated — you know at creation time exactly when they should land.
- Alerts. Server down, payment failed, a high-value lead just filled in a form. These are immediate — fire on an event.
- Drip sequences. Onboarding messages spaced out over days, re-engagement after a period of silence. A mix of both: you compute the send times up front and queue them.
The immediate-alert case is well served by basically every messaging API on the market. The reminder and drip cases are where scheduling matters, and where you usually end up running your own cron job, a queue, and a worker just to hold a message until its time comes. The appeal of a scheduling endpoint is that you delete all of that infrastructure and let the API hold the message for you.
The landscape: official Cloud API vs unofficial own-number APIs
Before any code, it's worth being clear about what kind of API this is, because the category determines everything downstream — cost, setup friction, and risk.
Meta's WhatsApp Cloud API (the official Business Platform, also resold by Twilio, 360dialog, and others) is the sanctioned path. You register a business, verify it, get a dedicated business phone number, and send through Meta's infrastructure. It's reliable and it's the right choice for high-volume, compliance-sensitive sending. The costs are conversation- and template-based, and that pricing model has been shifting toward per-message charges — I dug into the numbers in our WhatsApp Business API pricing breakdown for 2026. The friction is real: business verification, template approval, and a separate number you have to provision.
Unofficial own-number APIs take a different route. Instead of Meta's servers, they drive WhatsApp Web on your behalf — the same protocol your phone uses when you open WhatsApp in a browser tab. The message goes out from your existing, already-active number. That means:
- No Meta Business verification.
- No template pre-approval and no per-template fees.
- Messages come from the number your contacts already recognize.
Blueticks sits in this second category. It is not Meta's Cloud API and not the official Business Platform — it's an unofficial WhatsApp-Web transport running on your own number. That's the differentiator, and it's also where the honesty section later in this article comes in, because an own-number transport carries flagging and Terms-of-Service risk that the official API does not. Use the right tool for your volume and risk tolerance.
If you're exploring the AI-assistant angle on top of all this, there's a parallel ecosystem of WhatsApp MCP servers worth a look — but for plain backend integration, REST is what you want.
Authentication: getting an API key
Authentication is a single bearer token. You create an API key in your Blueticks account, and every request carries it in the Authorization header:
Authorization: Bearer bt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are environment-scoped. A live key is prefixed bt_live_ and a test key bt_test_, so you can wire up a sandbox path in CI without touching production. Keep the key server-side — it authenticates as your workspace and your WhatsApp number, so it does not belong in a browser bundle or a mobile app.
The base URL for every endpoint below is:
https://api.blueticks.co

Send a WhatsApp message via API
Let's start with the immediate send. The endpoint is POST /v1/scheduled-messages — yes, the same endpoint handles both immediate and scheduled sends; the only difference is whether you include a send_at field. Omit it, and the message goes out now.
The body is a small JSON object. The required fields are type (the message kind — text, media, or poll), to (an E.164 phone number like +14155551234, or a WhatsApp chat id), and the content for that type. Here it is as a raw HTTP call:
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
-H "Authorization: Bearer bt_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+14155551234",
"type": "text",
"text": "Your order #1024 has shipped. Track it here: https://example.com/t/1024"
}'
The response echoes back an id (the internal message id you can poll), a key (the WhatsApp wire id, populated once the message actually dispatches), a status, and a set of lifecycle timestamps (created_at, sent_at, delivered_at, read_at, failed_at).
There are official SDKs if you'd rather not hand-roll HTTP. In Python, install the client and call the typed method:
# pip install blueticks
from blueticks import Blueticks
client = Blueticks(api_key="bt_live_...")
msg = client.scheduled_messages.create(
to="+14155551234",
type="text",
text="Your order #1024 has shipped.",
)
print(msg.id, msg.status)
And the equivalent in Node (TypeScript or JavaScript):
// npm install blueticks
import { Blueticks } from "blueticks";
const client = new Blueticks({ apiKey: "bt_live_..." });
const msg = await client.scheduledMessages.create({
to: "+14155551234",
type: "text",
text: "Your order #1024 has shipped.",
});
console.log(msg.id, msg.status);
Both SDKs map directly onto the REST shape, so anything you can express in JSON you can express through them. Beyond text, the same create call accepts type: "media" (with an https media URL or base64 bytes) and type: "poll" — covered in the API reference.
Schedule a message for later — the differentiator
Here's the part that's genuinely hard to find elsewhere. To schedule rather than send, add a single field: send_at, an ISO 8601 timestamp with offset. The API holds the message and dispatches it at that time. No cron job, no queue, no worker process of your own.
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
-H "Authorization: Bearer bt_live_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+14155551234",
"type": "text",
"text": "Reminder: your appointment is tomorrow at 10:00am.",
"send_at": "2026-07-01T09:00:00+00:00"
}'
In Python:
from datetime import datetime, timedelta, timezone
from blueticks import Blueticks
client = Blueticks(api_key="bt_live_...")
send_time = datetime.now(timezone.utc) + timedelta(days=1)
client.scheduled_messages.create(
to="+14155551234",
type="text",
text="Reminder: your appointment is tomorrow at 10:00am.",
send_at=send_time.isoformat(),
)
In Node:
import { Blueticks } from "blueticks";
const client = new Blueticks({ apiKey: "bt_live_..." });
const sendAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
await client.scheduledMessages.create({
to: "+14155551234",
type: "text",
text: "Reminder: your appointment is tomorrow at 10:00am.",
send_at: sendAt,
});
A couple of constraints worth knowing, both enforced by the API: send_at must be at least 10 seconds in the future (so you can't accidentally schedule into the past) and at most 365 days out. The message comes back with status: "scheduled" until its time arrives.
Because the message lives as a real queued record, you can also manage it after the fact. GET /v1/scheduled-messages/{id} reads its current status; a PATCH to the same id lets you edit the text or move the send_at while it's still pending; and there's a cancel path to pull it back before it fires. That edit-and-cancel window is what makes the scheduling endpoint useful for things like "remind the customer 24h before, unless they pay first" — you cancel the reminder when the payment webhook lands.

Bulk sends: audiences and campaigns
Sending one message at a time is fine for transactional traffic. For one-to-many — a broadcast, a promotion, a notice to a segment — there are two building blocks.
An audience is a named list of contacts. You create it once and append contacts to it, optionally attaching custom variables to each contact for personalization:
audience = client.audiences.create(
name="July reminders",
contacts=[
{"to": "+14155551234", "variables": {"first_name": "Sam"}},
{"to": "+14155555678", "variables": {"first_name": "Alex"}},
],
)
A campaign then sends one message to every contact in an audience, substituting those variables with {token} syntax in the template:
client.campaigns.create(
name="July reminder blast",
audience_id=audience.id,
text="Hi {first_name}, a quick reminder that your renewal is coming up.",
)
Built-in tokens like {phone} and {displayname} are always available, and you choose what happens when a contact is missing a variable the template references — fail the whole campaign up front, or skip the substitution. Campaigns can be paused, resumed, and cancelled while they run.
One pacing note that matters here more than anywhere else: this is an own-number transport, not a bulk-SMS gateway. Blasting a large list at full speed is exactly the behavior that gets a number flagged. Start small, watch your delivery, and scale up gradually. More on that below.
Webhooks for delivery and replies
Polling a message id for its status works, but webhooks are cleaner. Register an endpoint and a list of events, and Blueticks will POST to your URL as things happen:
hook = client.webhooks.create(
url="https://your-app.example.com/webhooks/blueticks",
events=["message.delivered", "message.read", "message.failed"],
)
# hook.secret is returned once — store it to verify signatures.
The event catalogue covers the message lifecycle (message.queued, message.sending, message.delivered, message.read, message.failed), campaign progress (campaign.started, campaign.completed, campaign.aborted, and the pause/resume pair), and session state (session.connected, session.disconnected). Crucially for two-way use cases, there's also an inbound event so you can react to replies — when someone messages your number back, your endpoint hears about it.
Every delivery is signed. The request carries an X-Blueticks-Signature header of the form sha256=<hex>, which is an HMAC-SHA256 of the raw request body keyed with the secret you got at registration. Verify it before trusting the payload:
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
You can rotate a webhook's secret without re-registering, and disable a hook without deleting it.
Limits, pacing, and ban risk — the honest part
I'd be doing you a disservice if I skipped this. An own-number, WhatsApp-Web transport is not the official Business Platform, and that difference is not just about features:
- There is no "unlimited, guaranteed" sending here, and I won't pretend otherwise. WhatsApp actively watches for spammy patterns, and a number that suddenly sends hundreds of unsolicited messages can get rate-limited or banned. That risk is inherent to the category, not specific to any one provider.
- Pace yourself. Send to people who expect to hear from you, ramp volume gradually, and keep your content relevant. The APIs above will happily accept a large campaign — restraint is on you.
- It runs on your existing number, which is the upside (recognizable sender, no verification) and the thing you're putting at risk if you abuse it. Treat it accordingly.
- "No per-template fees / no verification" is true relative to Meta's model — there are no template-approval fees because there are no Meta templates in the path. That's a genuine cost advantage for the right use case, not a loophole around messaging hygiene.
On the plan side: the Free plan can send bulk campaigns (the one-message limit applies to scheduled messages, not campaigns) but appends a "Powered by blueticks.co" footer, and a paid Pro plan removes that branding and unlocks the always-on offline mode so sends fire even when your computer is closed. Match the plan to whether branding and 24/7 delivery matter for your integration.
For idempotency on retries, the send endpoint accepts an Idempotency-Key request header — pass a stable key and a network retry won't double-send. API keys also carry scopes (messages:write, campaigns:write, and so on), so you can mint a key that can only do what a given service needs.
FAQ
Is this the official WhatsApp Business / Cloud API? No. It's an unofficial WhatsApp-Web transport that sends from your own already-active number. If you need Meta's sanctioned, verified, high-volume platform, that's a different product (see the pricing breakdown for that path).
Can I really schedule a message for a future date over the API?
Yes — include a send_at ISO 8601 timestamp on the send call. It has to be at least 10 seconds out and within 365 days. The message is held server-side and you can edit or cancel it before it fires.
Which languages have SDKs?
There are official clients for Python and Node, both installed as blueticks, plus additional language SDKs. Everything is also reachable as plain REST, so any HTTP client works.
Do I need to verify a business with Meta? No — that's the point of the own-number model. The trade-off is the flagging/ToS risk discussed above, which the official API doesn't carry.
Can I receive replies, not just send? Yes, via webhooks. Register an inbound message event and your endpoint is notified when someone replies to your number.
Will my number get banned? It can, if you send spammy or unsolicited volume. Pace your sends, message people who expect to hear from you, and ramp up gradually. No provider can guarantee zero risk on an own-number transport.
Build it
If you want to send or schedule WhatsApp messages from your own code — on your own number, without the Meta verification dance — the full API reference, the SDK installs, and an interactive sandbox are at dev.blueticks.co. Grab a bt_test_ key, fire the curl call from the send section above, and you'll have a message scheduled in about two minutes.




