Channels

WhatsApp Business API onboarding

Meta setup, phone verification, template approval, opt-in.

The end-to-end path

1. Meta Business Manager — you (the customer) create/verify your Business account and give us the Business ID. 2. WhatsApp Business Account (WABA) — we create a WABA under your Business ID via Embedded Signup, so tokens live in your assets, not ours. 3. Phone verification — a dedicated number (mobile, landline, or hosted). SMS/voice verification, then registration. 4. Display name approval — Meta reviews the sender display name; usually approved same-day. 5. Template messages — for anything outside a 24h session window. We draft them; Meta approves in 24–48h. 6. Opt-in capture — you need explicit opt-in for every recipient before sending template messages. We wire this into your forms.

What we handle for you

  • Embedded Signup flow
  • Webhook subscription (X-Hub-Signature verification, retry queue)
  • Template drafting + submission
  • Quality-rating monitoring (auto-pauses sends if rating drops to yellow)
  • Rate-limit backoff and 24h window logic

Timeline

From kickoff to first production message: 5–7 business days for a standard setup.

Step-by-step: getting a template approved

1. Draft in our template studio — pick name, category (UTILITY / MARKETING / AUTHENTICATION), language, and components (header, body, footer, buttons). 2. Peer review against Meta's policy checklist: no promotional wording in UTILITY, every variable has a sample value, no URL shorteners in the body, media headers are hosted on a stable CDN, buttons stay within category limits. 3. Submit via the WhatsApp Business Management API: POST https://graph.facebook.com/v20.0/{waba-id}/message_templates. 4. Wait on the status webhook (message_template_status_update). Typical times: UTILITY ~2h, AUTHENTICATION ~4h, MARKETING 12–48h. 5. On APPROVED, tag the version in Git (templates/<name>/vN.json) and start routing new sends to it. The previous version stays live for a 7-day grace window. 6. On REJECTED, the webhook returns a rejection reason (policy category + free text). Fix it and resubmit as a new version — Meta does not allow editing a rejected template in place.

Example template payloads

1. Order update (UTILITY, header + body + footer)

{
  "name": "order_update_v3",
  "language": "en_US",
  "category": "UTILITY",
  "components": [
    { "type": "HEADER", "format": "TEXT", "text": "Order {{1}} update", "example": { "header_text": ["A1042"] } },
    { "type": "BODY", "text": "Hi {{1}}, your order {{2}} is now *{{3}}*. Estimated delivery: {{4}}.", "example": { "body_text": [["Nadia", "A1042", "out for delivery", "today, 6–8 pm"]] } },
    { "type": "FOOTER", "text": "Reply HELP for support." }
  ]
}

2. Appointment reminder (UTILITY, quick-reply buttons)

{
  "name": "appointment_reminder_v2",
  "language": "en_US",
  "category": "UTILITY",
  "components": [
    { "type": "BODY", "text": "Hi {{1}}, this is a reminder for your appointment tomorrow at {{2}}.", "example": { "body_text": [["Rifat", "4:30 pm"]] } },
    { "type": "BUTTONS", "buttons": [
      { "type": "QUICK_REPLY", "text": "Confirm" },
      { "type": "QUICK_REPLY", "text": "Reschedule" }
    ] }
  ]
}

3. Marketing broadcast (MARKETING, media header + CTA URL)

{
  "name": "weekly_drop_v1",
  "language": "en_US",
  "category": "MARKETING",
  "components": [
    { "type": "HEADER", "format": "IMAGE", "example": { "header_handle": ["<uploaded-handle>"] } },
    { "type": "BODY", "text": "New drop is live, {{1}} — 20% off for the next 24 hours.", "example": { "body_text": [["Nadia"]] } },
    { "type": "BUTTONS", "buttons": [
      { "type": "URL", "text": "Shop the drop", "url": "https://shop.example.com/drop/{{1}}", "example": ["autumn-2026"] }
    ] }
  ]
}

4. Authentication OTP (AUTHENTICATION, one-tap autofill)

{
  "name": "login_otp_v1",
  "language": "en_US",
  "category": "AUTHENTICATION",
  "message_send_ttl_seconds": 600,
  "components": [
    { "type": "BODY", "add_security_recommendation": true },
    { "type": "FOOTER", "code_expiration_minutes": 10 },
    { "type": "BUTTONS", "buttons": [
      { "type": "OTP", "otp_type": "ONE_TAP", "text": "Copy code", "autofill_text": "Autofill", "package_name": "com.example.app", "signature_hash": "K8a...==" }
    ] }
  ]
}

Versioning & lifecycle notes

  • Every template is a file in Git: templates/<name>/vN.json. The name deployed to the WABA is <name>_vN so multiple versions can co-exist during a rollout.
  • Quality rating (Green → Yellow → Red) is tracked per template on the webhook message_template_quality_update. A drop to Yellow automatically pauses new sends for that template until a human reviews.
  • Messaging tier limits cap how many unique users you can start conversations with per 24h: Tier 1 = 1,000 · Tier 2 = 10,000 · Tier 3 = 100,000 · Tier 4 = unlimited. Tier upgrades happen automatically as volume + quality hold.
  • Deprecating a template: mark paused: true in config → wait for in-flight sends to drain → DELETE /{waba-id}/message_templates?name=<name_vN> on day 7. Old versions stay auditable in Git either way.
  • Rejections are structural, not tuneable — if Meta rejects a MARKETING template as UTILITY-shaped (or vice versa), the fix is a category change, which is always a new version.