WhatsApp (Meta Cloud API)
The system sends and receives WhatsApp through Meta's Cloud API on the business number +91 95419 10494. This page is what is set up, what each secret is for, and what is still to do. Rules: AUD-021 (secrets are server-side only), AUD-022 (consent), ACC-069 (the password reset code), TRV-017 (sign-up in the chat), COMM-030 … COMM-036 (the WhatsApp menu).
What is set up
| Thing | Value or place |
|---|---|
| WhatsApp Business Account (WABA) | 1612279720367918 (the account that owns the number since 28/09/2026; was 4282145768584317) |
| Phone Number ID | 1245087675364304 |
| Number | +91 95419 10494, registered |
| Webhook | https://yzpfwdxpwalmfuodkxni.supabase.co/functions/v1/whatsapp-webhook, subscribed to messages |
| Webhook check | Meta's X-Hub-Signature-256 against WHATSAPP_APP_SECRET; the platform's JWT check is off for this function (verify_jwt = false), because Meta sends no login |
| App mode | Development — messages reach only the test numbers added in Meta until the app is Live |
Secrets (set with supabase secrets set, never in the repo or the frontend):
| Secret | What it is |
|---|---|
WHATSAPP_ACCESS_TOKEN |
A system-user token with whatsapp_business_messaging (and whatsapp_business_management, which syncing templates and setting up the sign-up form need). Rotate it in Meta Business Settings → System users if it is ever pasted anywhere. |
WHATSAPP_APP_SECRET |
The app secret, for checking webhook signatures |
WHATSAPP_PHONE_NUMBER_ID |
1245087675364304 |
WHATSAPP_VERIFY_TOKEN |
The string Meta echoes when it verifies the webhook URL |
WHATSAPP_DEFAULT_COUNTRY_CODE |
91. A number typed without a country code belongs to India; a trunk zero (09419…) is dropped |
WHATSAPP_OTP_TEMPLATE_NAME |
The approved authentication template used for codes (below). Unset until the template is approved |
WHATSAPP_TEMPLATE_LANGUAGE |
en_US unless the template was made in another language |
Who sends what
| Message | Sent by | How |
|---|---|---|
| Staff messages from the Communications screen | whatsapp-send (needs communications.send) |
Free text inside 24 hours of the customer's last message; an approved template row from WhatsAppTemplate otherwise — copied from Meta (below) |
| Booking and payment notifications queued by the system | communications-dispatcher → whatsapp-send |
Same rules |
| Automatic booking notices: booking confirmed, payment received with the receipt PDF | communications-dispatcher → whatsapp-booking-notice (service role) |
An approved template only, never free text; switched on in Admin → Reminders (below) |
| Password reset code for a customer or partner | auth-login (service role) |
The authentication template, code in the body and on the copy-code button |
| Sign-up code for a new customer (TRV-016) | auth-login signup_code (service role) |
The same authentication template — its words are generic ("123456 is your verification code"), so no second template is needed |
| Finance PIN reset code | mailer (service role) |
The same template |
| Incoming messages, STOP / START | whatsapp-webhook |
Stored once per Meta message id; consent updated |
| The WhatsApp menu: the options, groups, enquiry questions, booking status, payment link, document requests (COMM-030 … COMM-036) | whatsapp-webhook (service role), in answer to the customer's message |
Free-form text and interactive lists and buttons inside the 24-hour window — no template. Off until switched on (below) |
| Sign-up in the chat: the form, "already has an account", the set-password link (TRV-017) | whatsapp-webhook (service role), in answer to "sign up" |
Free-form replies inside the 24-hour window the customer's own message opens — no template. The form is a WhatsApp Flow (below) |
Message templates
Rule: AUD-024. WhatsApp only lets you message someone who hasn't written in 24 hours with a template Meta has approved. Until a template is in the system, staff see "No approved templates yet" in the WhatsApp inbox. Templates are made in Meta and copied in; nobody types them into the database.
To add a template
- Admin → Integrations → WhatsApp → Message templates → Open Meta template manager (or Meta Business Suite → WhatsApp Manager → Message templates, for the account whose ID is saved as Business Account ID on the same card).
- Create template. Category Utility for anything about a booking the customer already
has; Marketing for offers (those need the customer's opt-in, AUD-022). Language English
(Meta calls it
en, oren_USfor "English (US)" — either works; the system sends the one Meta holds). - Write the body with numbered variables
{{1}},{{2}}… (or named ones). Give each an example — the example is what staff see as the label above the box they fill in. A variable calledname(named format) is filled with the customer's name automatically. - Keep to a text header without a variable, or no header, and buttons without a variable (quick replies, a fixed website link, a phone number). A header picture, a header variable or a link button with a variable are listed in the system but cannot be sent from chat yet. A Document header is for the automatic booking notices only (below).
- Submit. Utility templates are usually approved within minutes to a day.
- Back in Admin → Integrations → WhatsApp → Message templates, press Sync from Meta
(
admin.integrations.edit). The summary says what was added, updated, removed in Meta, and not usable in chat. Approved ones show Usable in chat and appear in the inbox and the Communications screen at once.
The sync also runs every night at 03:00 IST (pg_cron job alhuda-whatsapp-templates-sync), using
the dispatcher's Vault secrets communications_dispatcher_url and communications_dispatcher_key
(deploy runbook → Secrets) — nothing new to store. Until those are stored
it logs "not configured" and does nothing; the button still works.
What the sync does: it reads every page of GET /{waba-id}/message_templates from Meta with the
access token in the request header (never in the URL, never in the browser, AUD-021) and keeps
one row per template name and language. Approved templates that need only body text are usable;
pending, rejected, paused or disabled ones are kept with their status; authentication templates
(the password reset code) are listed as "for one-time codes only"; a template deleted in Meta is
marked removed and kept, because old messages name it. Each run is one audit row
(whatsapp_templates.sync, or whatsapp_templates.sync_failed with Meta's reason).
If the sync fails, the card shows Meta's reason in plain words. The usual ones: the access token expired (Meta code 190 — make a new system-user token), or the Business Account ID is wrong (Meta code 100 — copy it from Meta Business Settings → Accounts → WhatsApp accounts).
Suggested starter templates (suggestions only — nothing has been created in Meta). All category Utility, language English:
| Name | Body |
|---|---|
booking_confirmation |
Assalamu alaikum {{1}}, your booking {{2}} for {{3}} is confirmed. We will send your travel documents before departure. Reply here with any question. — Alhuda Travels |
payment_reminder |
Assalamu alaikum {{1}}, a payment of {{2}} for booking {{3}} is due on {{4}}. Reply here if you have already paid or need the bank details. — Alhuda Travels |
departure_reminder |
Assalamu alaikum {{1}}, your group {{2}} departs on {{3}}. Please be at {{4}} by {{5}} with your passport. Reply here with any question. — Alhuda Travels |
document_request |
Assalamu alaikum {{1}}, to continue with booking {{2}} we need: {{3}}. Please reply to this message with a clear photo or PDF. — Alhuda Travels |
general_followup |
Assalamu alaikum {{1}}, this is Alhuda Travels about {{2}}. Please reply to this message so our team can continue the conversation with you here. |
general_followup is the one to use when a customer has gone quiet: once they reply, the 24-hour
window opens and staff can write freely.
Automatic booking notices
Rules: COMM-020 … COMM-025. What each says and who gets it: Booking WhatsApp notices. The system sends two WhatsApp messages by itself — booking confirmed (finance approves the booking) and payment received (finance verifies a payment, or an online payment is recorded). Both are off until an administrator switches them on.
1. Create the templates in Meta
Meta Business Suite → WhatsApp Manager → Message templates → Create template, for the
account saved as Business Account ID. All four: category Utility, language English,
positional variables {{1}} … {{5}} in exactly this order, and an example for each variable
(invented values). Create at least one template per notice; the names are the defaults and can be
changed in the settings.
| Name | Header | Body |
|---|---|---|
payment_receipt |
Document (upload any sample PDF as the example) | Dear {{1}}, we have received ₹{{2}} on {{3}} for booking {{4}}. Balance due: ₹{{5}}. Your receipt is attached. — Alhuda Travels |
payment_receipt_text |
none | Dear {{1}}, we have received ₹{{2}} on {{3}} for booking {{4}}. Balance due: ₹{{5}}. — Alhuda Travels |
booking_confirmed_text |
none | Assalamu alaikum {{1}}, your booking {{2}} for {{3}} ({{4}}) is confirmed. Travellers: {{5}}. — Alhuda Travels |
booking_confirmed |
Document | Assalamu alaikum {{1}}, your booking {{2}} for {{3}} ({{4}}) is confirmed. Travellers: {{5}}. Your booking confirmation is attached. — Alhuda Travels |
Which ones matter today. The receipt is attached to the payment notice, so create
payment_receipt (and payment_receipt_text as the fallback when the PDF cannot be made).
There is no booking confirmation PDF and no invoice PDF in the system yet, so the booking
confirmed notice always goes with booking_confirmed_text and attaches nothing. The
booking_confirmed document template is not used until a confirmation PDF exists; with only that
one approved, the notice is skipped ("No PDF to attach").
What fills each variable:
| Booking confirmed | Payment received | |
|---|---|---|
{{1}} |
the recipient's name (the partner on a partner booking) | the recipient's name (the payer when someone else pays) |
{{2}} |
booking number, BK-00123 |
amount, 1,00,000.00 (the ₹ is in the template) |
{{3}} |
the departure's name, else the package | payment date, 27/09/2026 |
{{4}} |
travel dates, 05/12/2026 to 19/12/2026 |
booking number |
{{5}} |
the travellers, Irfan Ahmad Dar, Shazia Dar and Umar Dar (six names, then "& N others") |
balance due, 2,15,000.00 |
A template with a different number of variables is not sent (logged as failed). A payment in another currency than rupees gets no notice.
2. Sync and check
Admin → Integrations → WhatsApp → Message templates → Sync from Meta. A document-header template shows as not usable in chat — that is expected; the notices can still use it. Then Admin → Reminders → Automatic WhatsApp notices shows each name with what Meta holds: Approved · document header, Approved · text only, Not synced from Meta yet, Not approved in Meta (pending), or a wrong header.
3. Switch on
On the same card switch Booking confirmed and/or Payment received on and press Save
(admin.integrations.edit; it asks first). From then on each approval or verification queues one
notice; the dispatcher sends it at once when the approval or verification was made on the finance
screen, else at its next run (every five minutes; an online payment waits for that run). While the app is in Development mode only Meta test numbers receive it.
How the PDF is attached
For a payment notice with payment_receipt approved, whatsapp-booking-notice asks
issue-document (service key) for the payment's receipt — the one already issued, or a new one,
kept on the Shared Drive under Issued/<booking> (FIN-045). It reads the PDF from the Drive with
the service account, uploads the bytes to Meta (POST /{phone-number-id}/media,
messaging_product=whatsapp, type=application/pdf) and sends the template with the returned
media id and a file name such as Receipt-RCP-00012-BK-00123.pdf in the Document header. No link
to the file is made and nothing is made public (ACC-073). Meta keeps uploaded media for 30 days.
The Google Drive secrets (GOOGLE_DRIVE_*) must be set on whatsapp-booking-notice too — they
are project-wide secrets, so nothing extra is needed once issue-document works.
When a notice does not go
Every notice is logged; the card's Last 50 WhatsApp notices shows the result. A failed notice, and one skipped because no template is approved (Template not approved yet) or no PDF can be attached, is also listed under Integrations → WhatsApp → WhatsApp delivery problems with Meta's code. Skipped for a reason that is not a fault: the customer replied STOP, no mobile number, the number is blocked in the inbox, the booking or payment changed before sending, the notice was switched off. A notice goes once: approving again, a retry or a second run sends nothing more.
The authentication template (to do)
Codes go out as a Meta authentication template; Meta refuses an authentication message that is not one. Until it exists and is approved, the reset page tells the person "WhatsApp codes are not switched on yet" and the email link is the only way.
- Meta Business Suite → WhatsApp Manager → Message templates → Create.
- Category Authentication, name
alhuda_password_reset, language English (US). - Body: the standard "{{1}} is your verification code." Add the security recommendation and the expiry line (10 minutes).
- Button: Copy code.
- Submit. Approval is usually minutes for authentication templates.
supabase secrets set WHATSAPP_OTP_TEMPLATE_NAME=alhuda_password_resetand redeploy nothing — the functions read the secret on each call.- Test from alhudatravels.in/customer/auth → Forgot password? → Get a code on WhatsApp with a customer whose phone is a Meta test number (while the app is in Development).
- The same template carries the sign-up code. Test it from /customer/auth → Sign Up → Mobile (WhatsApp) with a Meta test number that has no account yet. Until the secret is set, the sign-up page says WhatsApp codes are not switched on and offers the email sign-up.
Sign-up in the chat (WhatsApp Flows)
A customer makes an account by writing "sign up" to the business number (TRV-017). "register", "account", "register karna", "account banana hai" and the like work too. It makes a login only — no orders or bookings over WhatsApp.
What the customer sees
- They tap the link or scan the QR code; WhatsApp opens a chat with Alhuda Travels with "Sign up" already typed. They press send.
- A message comes back: "Assalamu alaikum! Tap Create account to make your Alhuda Travels account with this WhatsApp number." with a Create account button.
- The button opens a form inside WhatsApp, Create your Alhuda account: full name, city (optional), email (optional) and I agree to the privacy policy (with a link to the policy). They tap Create account.
- A message comes back: "Your Alhuda account is ready. Set your password here: ". The link works once, for 30 minutes.
- The link opens
alhudatravels.in/customer/set-password. They type a password twice and are signed in to the customer portal. From then on they sign in on the website or in the app with their mobile number and password.
A number that already has an account gets "This number already has an Alhuda account. Sign in at https://alhudatravels.in/customer/auth or in the Alhuda app …" instead of the form. A link that has expired: they sign in with Forgot password → WhatsApp code.
Set it up once (after the release)
- Make sure the WhatsApp access token's system user has
whatsapp_business_managementon the WhatsApp account (Meta Business Settings → System users → the user → Assign assets). The template sync needs the same permission. - Admin → Integrations → WhatsApp → Sign-up in the WhatsApp chat → Set up WhatsApp
sign-up form (needs
admin.integrations.edit). It creates the form on the WhatsApp account (category Sign up), uploadssupabase/functions/_shared/whatsappSignupFlow.json, publishes it and saves its id (CommunicationSettingkeywhatsapp_signup_flow_id). The card then says Published. Pressing it again changes nothing. - If Meta refuses, the card shows Meta's words and what to do (for example "the token may not manage Flows"). Fix it and press the button again; it does only what is missing.
- Test: from a phone whose number has no account (and, while the app is in
Development, is on Meta's test list), open
https://wa.me/919541910494?text=Sign%20upand send. Fill the form. Open the link that comes back and set a password. Then write "sign up" again from the same phone: the answer is "This number already has an Alhuda account".
Until the form is published, "sign up" in the chat is answered with a pointer to the website's sign-up page.
For marketing. The link is https://wa.me/919541910494?text=Sign%20up (the card has a
Copy button). Make the QR code from this link with any QR code maker (WhatsApp Manager can
also make one for the number with "Sign up" filled in, under the number's message links / QR
codes). The system does not draw the QR code itself.
Limits. One form a minute and five an hour per number; after five sign-up replies in 15 minutes a number gets no reply until the window passes. A form works for 30 minutes, once, and only from the number it was sent to. A reply WhatsApp refused shows on WhatsApp delivery problems (below).
The WhatsApp menu
A customer who writes to the business number gets a list of options — Groups available, Booking enquiry, My booking status, Pay balance, Documents needed, Talk to the office (and Sign up for a number with no account). The messages, word for word: WhatsApp menu. Rules: COMM-030 … COMM-036. It ships off; until it is switched on every message waits for a person, as before.
Switch it on
- Migration
20261005160000_a_customer_uses_the_whatsapp_menu.sqlis applied andwhatsapp-webhookandrazorpay-orderare deployed (the deploy runbook does both). - Admin → Integrations → WhatsApp → WhatsApp menu (needs
admin.integrations.edit): type the office hours (default Mon–Sat 10:00–18:00 IST) and the bank details customers should pay into (account name, bank, account number, IFSC, UPI ID — sent as typed; left empty, customers are told to ask the office). Turn the switch on and press Save WhatsApp menu; give a reason. The change is audited. - For the payment link, the Razorpay secrets
RAZORPAY_KEY_IDandRAZORPAY_KEY_SECRETmust be set (the same ones the app uses). Without them the menu says online payment isn't available and gives the bank details only. - For documents, the Google Drive secrets must be set (they are — the same ones every upload uses). The WhatsApp token fetches the customer's photo from Meta.
- Test from a phone on Meta's test list (while the app is in Development) whose number is on a test booking: write hi; try each option; send a photo when asked for a passport page; open the payment link. Then write Talk to the office and reply from the inbox.
To switch it off, turn the switch off and save. "sign up" keeps working either way.
What staff see
- WhatsApp inbox: the whole conversation, the menu's replies included (a reply the menu sent has no sender). A conversation the menu is handling is not counted unread.
- Work inbox: a WhatsApp enquiry lead for each booking enquiry (sales pool, 30 working minutes); a WhatsApp: asked for the office item when a customer chooses Talk to the office; WhatsApp: bank receipt sent when a customer sends a receipt after Pay balance; and, as before, any message the menu did not answer (a photo nobody asked for, a partner's message, anything said while the conversation is with the office).
- Customer 360 → Files: passport pages, photos and receipts sent on WhatsApp, marked
From WhatsApp · for
, Awaiting review. Press OK or Reject (say what is wrong — the customer is asked again next time). Type the passport details on the booking yourself; nothing is read from the photo. - Finance: a receipt photo is not a payment. Record it with Record payment once the money is in the bank, as for any transfer. A payment made through the link is recorded by Razorpay's signed event, like the app's.
While staff talk to the customer the menu is quiet. It answers again when the customer writes menu, or 30 minutes after the last staff reply when the customer has also been quiet for 30 minutes.
Limits. At most 60 menu replies an hour per number; three enquiries a day per number; five payment links an hour per number; a link works once for 30 minutes; files up to 10 MB, photos or PDF only.
Going Live (to do)
Meta's app review needs the privacy policy URL https://alhudatravels.in/privacy (it
names WhatsApp in §9) and a business verification. Until Live, only the test numbers
listed in the app receive messages; a real customer sees nothing, and a refused reset
code shows on Admin → Integrations → WhatsApp with Meta code 131030.
If something fails
-
A code "is on its way" but nothing arrives. Open Admin → Integrations → WhatsApp → WhatsApp delivery problems (needs
admin.integrations.view). Every reset code Meta refused in the last 30 days is listed with whose account it was, the last two digits of the number it went to, Meta's error code, Meta's words (numbers masked) and a plain hint:Meta code Hint on the screen 131030 This number is not on the Meta test list — the WhatsApp app is still in Development mode 132001 Template name or language doesn't match an approved template 131026 Number is not on WhatsApp 190 WhatsApp access token expired 132000 The template takes a different number of values than were sent 132012 A value does not match the template's format (for example a document sent to a template made without a Document header) The same list shows the automatic booking notices that failed or found no approved template (above). It also shows the sign-up codes Meta refused, named Sign-up code (new customer) — the person has no account yet, so there is no name; the last two digits of the number are shown (TRV-016). A number that already has an account gets no sign-up code at all, so it never shows here: the person should sign in, or reset their password.
The sign-up replies in the chat WhatsApp refused are listed too (TRV-017): WhatsApp sign-up form (new customer), WhatsApp sign-up — set-password link and WhatsApp sign-up reply ("already has an account", or the pointer to the website). A set-password link that did not go: the account exists; the person signs in with Forgot password → WhatsApp code.
Nothing on the list and still no message: the account may have no number, or the code may have gone to the number the person typed on the reset page — which is the one they get since 2 Oct 2026 (ACC-069). Supabase's CLI has no
functions logscommand any more; the full error is also in the Supabase dashboard → Edge Functions →auth-login→ Logs. - A staff message is refused with "outside the 24-hour window". Use a template. No templates to choose from: create them in Meta and press Sync from Meta (above). - The webhook stops receiving. Meta disables a webhook after repeated non-200 answers; re-verify it in the app's WhatsApp → Configuration page. The verify token is theWHATSAPP_VERIFY_TOKENsecret.
Not done
- Creating or editing templates from the ERP. They are made in Meta and copied in. A template whose header takes a picture or a variable, or whose link button takes a value, is listed but cannot be sent from chat.
- A booking confirmation or invoice PDF on WhatsApp. No such PDF exists; the booking confirmed notice is text only (COMM-022).
- Orders and bookings over WhatsApp. The owner's standing rule; the sign-up in the chat makes an account and nothing else; the automatic notices are outbound only; the WhatsApp menu turns a booking enquiry into a lead for sales and takes no order or payment in the chat (COMM-032); other incoming messages become work items (WRK-002).
- The WhatsApp number is not in the email footer until the app is Live.