You wired up your first automated WhatsApp send in an afternoon. Then a webhook fired twice and a customer got the same reminder at 3am, twice. That gap between "it sends" and "it sends reliably, once, at the right time" is where most integrations live. This guide walks the whole path with a WhatsApp automation API: one-call sends, scheduled sequences in Python and Node, event triggers, and the idempotency trick that stops retries from double-sending.
What does automating WhatsApp with an API actually involve?
Automating WhatsApp with an API means calling a REST endpoint from your own code to send, schedule, and react to messages, instead of typing them by hand. In practice you need four moving parts: a send call, a way to schedule for later, event triggers from your app, and webhooks to hear back about deliveries and replies.
Most people start with only the first part and discover the other three the hard way. A single POST that fires a message is trivial. The workflow around it is where the work sits:
- Send. One authenticated call pushes a message out right now.
- Schedule. The same call, plus a future timestamp, holds the message until its time.
- Trigger. Your app's own events (a payment, a signup, a no-show) decide when to send.
- React. Webhooks tell you when a message was delivered, read, failed, or replied to.
Get all four talking and you have a real automation, not a script that fires and forgets. The own-number REST API walkthrough covers the transport underneath; this piece is about the automation on top.
Which WhatsApp automation API works on your own number without Meta approval?
An own-number WhatsApp automation API drives WhatsApp Web on your behalf, the same protocol your phone uses in a browser tab, so messages go out from your existing active number. That skips Meta Business verification, template pre-approval, and per-template fees entirely. The trade-off is that it is unofficial and carries flagging risk, which the sanctioned Cloud API does not.
There are two categories, and the choice shapes everything downstream:
| Own-number API (Blueticks /v1) | Meta Cloud API direct | |
|---|---|---|
| Sends from | Your existing WhatsApp number | A dedicated Cloud API number |
| Meta business verification | Not required | Required |
| Template pre-approval | Not required | Required outside the 24h window |
| Per-message fees | No | Yes, per Meta's pricing |
| Time to first automated send | Minutes | Days to weeks |
Meta moved WhatsApp Business to per-message pricing on July 1, 2025, deprecating the older conversation-based model, and free-form messages are only allowed inside a 24-hour window after the contact last messaged you. Blueticks sits in the first column: an unofficial WhatsApp-Web transport on your own number. It is not Meta's Cloud API, and that is the point.
How do you send an automated WhatsApp message with one API call?
To send a WhatsApp message from an API, you POST to https://api.blueticks.co/v1/scheduled-messages with a bearer key and a small JSON body: to (an E.164 number or WhatsApp chat id), type (text, media, or poll), and the content. Omit any future timestamp and the message goes out immediately. This one WhatsApp API send-message call is the core of every automation you will build.
Authentication is one header. Keys are environment-scoped, bt_live_ for production and bt_test_ for a sandbox, so you can wire a test path in CI without touching real sends:
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: https://example.com/t/1024"
}'
The response hands back an id you can poll, a key (the WhatsApp wire id, populated once it dispatches), a status, and lifecycle timestamps (created_at, sent_at, delivered_at, read_at, failed_at). Keep the key server-side. It authenticates as your workspace and your number, so it does not belong in a browser bundle or mobile app. The full send anatomy, including media and polls, is in the send-and-schedule API guide.

How do you schedule a message or a sequence from code (Python & Node)?
To schedule instead of send now, add one field to the same call: send_at, an ISO 8601 timestamp with offset. The API holds the message and dispatches it at that time, so you delete your own cron job, queue, and worker. For a sequence, compute each send time up front and fire one create call per step. Official Python and Node SDKs wrap the REST shape.
The scheduling constraint worth memorizing: send_at must be at least 10 seconds in the future and at most 365 days out. Here is a whatsapp api python schedule for a two-step onboarding drip, one message now and a follow-up in three days:
# pip install blueticks
from datetime import datetime, timedelta, timezone
from blueticks import Blueticks
client = Blueticks(api_key="bt_live_...")
# Step 1: welcome, immediately
client.scheduled_messages.create(
to="+14155551234", type="text",
text="Welcome aboard. Reply if you get stuck.",
)
# Step 2: nudge, three days out
send_at = (datetime.now(timezone.utc) + timedelta(days=3)).isoformat()
client.scheduled_messages.create(
to="+14155551234", type="text",
text="How's it going? Here's a 2-minute setup tip.",
send_at=send_at,
)
The Node SDK maps onto the identical shape:
// npm install blueticks
import { Blueticks } from "blueticks";
const client = new Blueticks({ apiKey: "bt_live_..." });
const sendAt = new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString();
await client.scheduledMessages.create({
to: "+14155551234", type: "text",
text: "How's it going? Here's a 2-minute setup tip.",
send_at: sendAt,
});
Because each scheduled message is a real queued record, you can manage it after creation. GET /v1/scheduled-messages/{id} reads its status, a PATCH to the same id edits the text or moves send_at while it is still pending, and a cancel path pulls it back before it fires. That edit-and-cancel window is what makes "remind them 24h before, unless they act first" a two-line change instead of a rebuild.

How do you trigger sends from your app's own events (payment, signup, no-show)?
You trigger a WhatsApp send from an app event by calling the send endpoint inside the handler that already runs for that event. A Stripe payment_succeeded webhook fires a receipt, a signup row inserts and fires a welcome, a no-show flag fires a rebook nudge. The API call is the same one-liner; the trigger is just where you put it in code you already have.
Here is the pattern inside a payment handler. When the charge clears, send the confirmation from your own number:
@app.post("/stripe/webhook")
def stripe_hook():
event = stripe_verify(request) # your existing verification
if event["type"] == "payment_intent.succeeded":
phone = event["data"]["object"]["metadata"]["wa_phone"]
client.scheduled_messages.create(
to=phone, type="text",
text="Payment received. Your booking is confirmed for Thursday 10am.",
)
return "", 200
Three trigger shapes cover most of what you will build:
- Immediate reaction. Payment cleared, lead submitted a form, server alert. Send now, no
send_at. - Future reminder off an event. Appointment booked today, reminder scheduled for the day before with
send_at. - Conditional cancel. Schedule the reminder at booking time, then cancel it if the "paid" or "confirmed" event lands first.
What breaks: if your event handler retries (and payment webhooks retry aggressively), you send twice. That is not hypothetical. It is the single most common failure in event-driven WhatsApp automation, and the next section is the fix.
If you would rather not run your own worker, cron, and queue just to hold these triggers, skip straight to a bt_test_ key and the send call above. Start on the free /v1 API: no Meta approval queue, no per-message fees, and every message goes out from the number your contacts already recognize.
How do you make automated sends idempotent so retries never double-send?
You make a send idempotent by passing a stable Idempotency-Key header. If a network blip or a retrying event handler replays the same request, the API recognizes the key and does not send a second time. The key just has to be deterministic for a given logical message, like the order id or the event id that caused the send.
Webhook and job delivery is at-least-once, never exactly-once, so retries are a certainty, not an edge case. The defense is a key your code can regenerate identically on the retry:
curl -X POST https://api.blueticks.co/v1/scheduled-messages \
-H "Authorization: Bearer bt_live_..." \
-H "Idempotency-Key: receipt-order-1024" \
-H "Content-Type: application/json" \
-d '{ "to": "+14155551234", "type": "text",
"text": "Payment received. Booking confirmed." }'
The rule of thumb: derive the key from the thing that must produce exactly one message. One receipt per order, so receipt-order-1024. One reminder per booking, so reminder-booking-8842. Never use a random UUID generated fresh on each attempt, because the retry generates a different one and defeats the whole mechanism. As one operator running appointment reminders put it: "We stopped keying on a timestamp and started keying on the booking id. The double-sends went to zero the same day." Pair the idempotency key with scoped API keys (messages:write) so a service that only sends cannot also delete audiences.
How do you react to replies and delivery status automatically with webhooks?
You react automatically by registering a webhook: one POST to https://api.blueticks.co/v1/webhooks with a url and an events array. Blueticks then POSTs to your endpoint as things happen, so a reply, a delivery, or a failure lands on your server without polling. Verify the signature, branch on event.type, and your automation closes the loop.
Register the events you care about:
curl -X POST https://api.blueticks.co/v1/webhooks \
-H "Authorization: Bearer bt_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/hook",
"events": ["message.delivered", "message.read", "message.failed"]
}'
The catalogue spans the message lifecycle (message.queued, message.sending, message.delivered, message.read, message.failed), campaign progress (campaign.started, campaign.completed, campaign.aborted), and session state (session.connected, session.disconnected). For two-way flows there is an inbound event so you can react to replies. Every delivery is signed: the X-Blueticks-Signature header is an HMAC-SHA256 of the raw body, and you must verify it over the exact bytes on the wire, using a constant-time comparison, before trusting the payload. The full Flask and Express handlers are in the webhooks auto-reply guide.

How do you keep automated WhatsApp sending reliable and unbanned?
Reliability on an own-number transport comes down to pacing, not throughput. WhatsApp watches for spammy patterns, and a number that suddenly blasts hundreds of unsolicited messages gets rate-limited or banned. Send to people who expect to hear from you, ramp volume gradually, and keep content relevant. No provider can guarantee zero risk on an own-number API, and any that claims to is lying.
A short pre-flight checklist keeps automations honest:
- Ramp, don't blast. Start with a small daily volume and scale up over days, watching delivery.
- Send to opted-in contacts. Reminders, receipts, and replies to people who messaged you are legitimate use. Cold outbound to strangers is what gets numbers flagged.
- Watch
message.failed. A spike is your early warning. Wire it to an alert. - Keep one WhatsApp Web session. Opening a second session elsewhere disconnects the one your automation runs on.
For 24/7 automation the machine matters too. On the Free plan a "Powered by blueticks.co" footer rides along and delivery depends on your browser being connected; the paid Pro plan removes the branding and unlocks an always-on offline mode so scheduled and triggered sends fire even when your computer is closed. Match the plan to whether branding and around-the-clock delivery matter for your integration. More on the own-number risk profile in the REST API own-number guide.
Where Blueticks fits: automation without running your own cron, queue, and worker
Blueticks fits the developer who wants triggered, scheduled, two-way WhatsApp automation on their own number today, without standing up a cron job, a queue, a worker, and a Meta verification process to hold a message until its send time. One /v1 API covers send, schedule, campaigns, and webhooks, so a single integration replaces the infrastructure you would otherwise build and babysit.
The concrete swap: instead of a scheduler service plus a durable queue plus a retry worker plus a Meta template pipeline, you make one authenticated call with an optional send_at and an optional Idempotency-Key. Scheduling lives server-side. Retries are safe. Replies and delivery status arrive as webhooks. For one-to-many, an audience holds a contact list with per-contact variables and a campaign sends one templated message to all of them with {token} substitution, paced for an own-number transport.
You still own the parts that should stay yours: which events trigger a send, what the copy says, and the pacing discipline that keeps your number healthy. Blueticks owns the plumbing. That division is the whole pitch of a WhatsApp automation API that runs on your number instead of Meta's.
The fastest way to send WhatsApp message from API and see it land is the sandbox: the interactive reference and SDK installs are at dev.blueticks.co. Grab a free bt_test_ key, fire the send call from earlier, and you will have an automated WhatsApp send running in about two minutes.
FAQ
What is a WhatsApp automation API?
It is a REST API you call from your own code to send, schedule, and react to WhatsApp messages automatically instead of sending them by hand. With Blueticks, one POST /v1/scheduled-messages call sends now or (with a send_at timestamp) later, and webhooks notify you of deliveries and replies. It runs on your own number, so there is no Meta business verification.
How do I schedule a WhatsApp message from Python?
Install the blueticks SDK, create a client with your API key, and call scheduled_messages.create with to, type, text, and a send_at ISO 8601 timestamp. The timestamp must be at least 10 seconds in the future and within 365 days. The message is held server-side and dispatched at that time, with no cron job of your own.
How do I stop retries from sending the same WhatsApp message twice?
Pass a stable Idempotency-Key header derived from the thing that must produce exactly one message, like an order or booking id. If a retrying event handler replays the request, the API recognizes the key and does not send again. Never use a fresh random UUID per attempt, because the retry generates a different key and defeats it.
Do I need Meta approval to automate WhatsApp sends?
No, not with an own-number API. Blueticks drives WhatsApp Web on your existing number, so you skip Meta business verification, template pre-approval, and per-message fees. The trade-off is that it is unofficial and carries flagging risk if you send spammy volume, which Meta's sanctioned Cloud API does not.
Can I trigger a WhatsApp send from a Stripe or signup event?
Yes. Call POST /v1/scheduled-messages inside the handler that already runs for that event, so a payment_succeeded webhook or a new signup fires a message from your own number. Add an Idempotency-Key because payment webhooks retry, and you avoid double-sends when the same event is delivered more than once.



