WhatsApp Operators DailyThe Blueticks DispatchSaturday, September 12, 2026
Productivity

WhatsApp Business API Opt-In: How to Collect, Record & Sync Consent in Code (2026)

The WhatsApp Business API never verifies consent for you. Here's what opt-in actually means at the API layer, how to collect it before your first call, and how to record and sync opt-in and opt-out programmatically with webhooks.

DRBy Daniel Roth · September 12, 2026 · 10 min read
WhatsApp Business API Opt-In: How to Collect, Record & Sync Consent in Code (2026)

You can send a WhatsApp Business API message to any number that resolves. That's the trap. The API will happily push a template to someone who never agreed to hear from you, and then your quality rating pays for it a day later. Consent isn't something the API enforces at send time. It's something you have to build, store, and keep in sync yourself. This guide covers the developer side of WhatsApp Business API opt-in: what "opt-in" means at the API layer, how to collect it before your first call, and how to record and sync opt-in and opt-out in code with webhooks.

One thing up front, because it frames everything below: opt-in is a rule about the sender, not the transport. It applies to you whether you send through Meta's Cloud API, a BSP, or from your own number. Blueticks sends from your own WhatsApp number over WhatsApp Web and exposes a /v1 REST API for it, so the patterns here are engine-agnostic on purpose.

What does "opt-in" mean on the WhatsApp Business API?

Opt-in means the person gave you their number and explicitly agreed to receive messages from you on WhatsApp before you send the first one. Two facts, not one: you have the number, and you have permission tied to that number.

Meta's WhatsApp Business Messaging Policy states you may message someone only if "they have given you their mobile phone number" and "you have received opt-in permission from the recipient confirming that they wish to receive subsequent messages or calls from you." The permission has to be affirmative (an action they took) and attributable (you can say who and when), obtained in a way that complies with the laws that apply to you. Meta leaves the method up to you and, since its late-2024 policy update, doesn't require the opt-in to name WhatsApp specifically - it can be a general opt-in as long as you follow local law. But the clearer you are that someone is agreeing to hear from your business on WhatsApp, the fewer blocks and reports you'll get - and blocks and reports are exactly what the enforcement below runs on.

That's the whole definition. What surprises most developers is where it gets checked.

Where does the API actually verify opt-in? (it doesn't)

Here's the part nobody documents clearly: the WhatsApp Business API does not verify consent at send time. There is no opt_in: true field you pass, no pre-send check that returns a 403 because a contact never agreed. You attest to consent by sending; the platform trusts you at the moment of the call.

Enforcement happens after, and indirectly:

  • Quality rating. Recipients who didn't opt in block or report you. Meta tracks that per number and drops your quality rating (green to yellow to red).
  • Messaging limits. A poor rating throttles how many unique users you can start conversations with per day.
  • Number bans. Sustained low quality gets the number flagged or disabled.

So the API's silence is not permission. It just means the cost of skipping opt-in is deferred and paid in deliverability, not in an error code. That's exactly why you build the consent layer yourself: the platform won't stop a bad send, so your own system has to.

Notebook sketch of a consent-then-send flow on a workspace, illustrating that opt-in is enforced by the sender, not the API

How to collect opt-in before your first API call

Collection happens off the API, on whatever channel the person is already on. The API is downstream of consent, never the source of it. The common channels, roughly by how strong the resulting proof is:

  • Click-to-WhatsApp / a wa.me link the person taps - they initiated, which is strong signal, but a tap to chat isn't automatically a subscription. Confirm intent in the first reply.
  • A checkout or signup field with an unchecked, specific consent box ("Message me on WhatsApp about my order").
  • A website opt-in widget with clear wording about what you'll send and how to stop.
  • In-store QR codes / tablets where the person enters their own number.
  • Double opt-in, where you send one confirmation request and require a reply before you count them - the strongest proof, because a mistyped digit never completes it.

We go deep on the channel mechanics and their real conversion trade-offs in the WhatsApp opt-in collection guide, and on the confirmation-step design in double opt-in for WhatsApp Business. For the legal shape of "specific and affirmative," see the opt-in compliance requirements. Below, we stay on the code: what you do with each opt-in once you have it.

The API forgets. Your database has to remember. For every opt-in, write a row you could show a regulator or Meta a year later. At minimum:

{
  "phone": "+15551234567",
  "opted_in": true,
  "source": "checkout_form",
  "consent_text": "Message me on WhatsApp about my order and offers",
  "timestamp": "2026-09-12T14:03:11Z",
  "ip": "203.0.113.7",
  "double_opt_in_confirmed_at": "2026-09-12T14:07:52Z"
}

The four fields that matter most are consent_text (the exact wording they agreed to), timestamp, source, and the confirmation time if you use double opt-in. "We have their number" is not proof of consent. "On 2026-09-12 they ticked this specific box from this source" is.

Key the record on the phone number in E.164 format (leading +, country code, no spaces) so it joins cleanly against every send and every inbound event. Store opt-out on the same row rather than deleting it - you need to prove you stopped, and a deleted row proves nothing.

Using webhooks to capture opt-in and opt-out replies

Where this gets automatic is inbound. Most real opt-ins and nearly all opt-outs arrive as a reply on WhatsApp itself: someone messages START to subscribe, or STOP to leave. Polling for those is hopeless. You want the event pushed to you the instant it lands, and that's what a webhook is - a URL on your server the platform POSTs to on every inbound message.

With Blueticks you point a webhook at your server through the /v1 REST API. The base URL is https://api.blueticks.co/v1, and every call authenticates with a key you mint in the dashboard, sent as Authorization: Bearer bt_live_.... One registration call and inbound WhatsApp messages start hitting your endpoint. (Full wiring, signature verification, and Flask/Express handlers are in receive and auto-reply with webhooks.)

Then your handler reads the sender and text and updates the consent row:

KEYWORDS_IN = {"start", "subscribe", "yes"}
KEYWORDS_OUT = {"stop", "unsubscribe", "cancel"}

@app.post("/webhooks/whatsapp")
def inbound():
    event = request.get_json()
    phone = event["from"]                     # E.164
    text = event.get("text", "").strip().lower()

    if text in KEYWORDS_OUT:
        set_consent(phone, opted_in=False, source="reply_stop")
        send_message(phone, "You're unsubscribed. Reply START to rejoin.")
    elif text in KEYWORDS_IN:
        set_consent(phone, opted_in=True, source="reply_start")
        send_message(phone, "You're subscribed. Reply STOP anytime to leave.")

    return "", 200

Two things make this correct rather than just functional. First, the reply itself is the proof - stamp the webhook's timestamp onto the consent row. Second, always acknowledge, especially the STOP, so the person sees the system heard them; a silent unsubscribe reads as being ignored and gets you reported.

Syncing opt-out: honor STOP everywhere, immediately

Opt-out is the half people ship late, and it's the half that burns numbers. The rule is simple and strict: the moment someone opts out, no further non-service message goes to them, from any part of your system.

That means the opt-out has to be authoritative, checked at send time by every path that can send:

def can_send_marketing(phone):
    row = get_consent(phone)
    return bool(row and row["opted_in"])

# before every outbound campaign message:
recipients = [p for p in audience if can_send_marketing(p)]

Put that check in one place every sender calls, not copy-pasted per script - that's how a stale queue re-messages someone who left an hour ago. If you run campaigns and one-off replies from different code paths, they both read the same consent table. The delivery-status webhooks close the loop on the outbound side, so you can also catch numbers that consistently fail and retire them.

Single filing tray on a tidy desk representing one authoritative consent record every sender checks before sending

Do you need API opt-in if you send from your own number?

Yes. This is the most common misread, so be clear-eyed about it: sending from your own number over WhatsApp Web (the way Blueticks works) keeps you outside Meta's Cloud API and its per-message metering, but it does not remove the consent obligation. The Business Messaging Policy applies to the sender regardless of transport, and the own-number path trades away Meta's verification and its ban/rate-limit protections - so unwanted messages get you blocked faster, not slower, because there's no BSP cushioning your quality signal.

What the own-number path does change is the plumbing, not the rule. You still collect opt-in the same way, still store the same proof, still honor STOP the same way. Blueticks gives you the /v1 API and webhooks to run that whole loop programmatically from a number you already own, without Meta Business verification. If verification is the wall you're trying to get around, sending without Meta verification covers what you gain and give up; for the outbound half, see scheduling and sending via the API.

FAQ

Does the WhatsApp Business API check that a contact opted in before I send? No. There's no consent field and no pre-send verification. The API sends what you tell it to. Consent is enforced after the fact through quality rating, messaging limits, and number bans, which is why you have to build and check the opt-in layer yourself.

How do I prove someone opted in? Store a record per contact with the exact consent wording they agreed to, a timestamp, the source channel, and (if you use it) the double opt-in confirmation time. Keep opt-out on the same record rather than deleting it, so you can prove you stopped.

What's the difference between opt-in for the Cloud API and for own-number sending? None, as a rule - the WhatsApp Business Messaging Policy binds the sender on every transport. The difference is only in the tooling and the safety net: the own-number path skips Meta's per-message billing and verification but also its ban protection, so consent discipline matters more, not less.

How do I capture STOP replies automatically? Register a webhook that receives inbound messages, match opt-out keywords (STOP, UNSUBSCRIBE, CANCEL) case-insensitively, flip the contact's consent to false with the webhook timestamp, and reply to confirm. Make every sender read that same consent table before sending.

Can I import an old contact list and start messaging? Not safely. A list without per-contact proof of WhatsApp opt-in is exactly what tanks quality ratings. Re-permission it: run a single confirmation request to numbers you have a lawful basis to contact, and keep only the ones who reply yes.

Consent is the sender's responsibility on every path, and this guide is engineering guidance, not legal advice - check your own regulatory obligations (GDPR, TCPA, local law) before you send.


Run the whole opt-in loop from your own number. Blueticks gives you a /v1 REST API and webhooks to collect, record, and sync WhatsApp consent - and to schedule and send campaigns from a number you already own, no Meta Business verification. Start free at blueticks.co/signup.

Email

The Dispatch, every week.

One sharp WhatsApp growth tactic in your inbox each week. Joined by 238,000+ founders, marketers and support leads.

Free forever. No spam, unsubscribe in one click.