Skip to content

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

  1. 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).
  2. 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, or en_US for "English (US)" — either works; the system sends the one Meta holds).
  3. 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 called name (named format) is filled with the customer's name automatically.
  4. 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).
  5. Submit. Utility templates are usually approved within minutes to a day.
  6. 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.

  1. Meta Business Suite → WhatsApp Manager → Message templates → Create.
  2. Category Authentication, name alhuda_password_reset, language English (US).
  3. Body: the standard "{{1}} is your verification code." Add the security recommendation and the expiry line (10 minutes).
  4. Button: Copy code.
  5. Submit. Approval is usually minutes for authentication templates.
  6. supabase secrets set WHATSAPP_OTP_TEMPLATE_NAME=alhuda_password_reset and redeploy nothing — the functions read the secret on each call.
  7. 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).
  8. 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

  1. They tap the link or scan the QR code; WhatsApp opens a chat with Alhuda Travels with "Sign up" already typed. They press send.
  2. A message comes back: "Assalamu alaikum! Tap Create account to make your Alhuda Travels account with this WhatsApp number." with a Create account button.
  3. 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.
  4. A message comes back: "Your Alhuda account is ready. Set your password here: ". The link works once, for 30 minutes.
  5. 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)

  1. Make sure the WhatsApp access token's system user has whatsapp_business_management on the WhatsApp account (Meta Business Settings → System users → the user → Assign assets). The template sync needs the same permission.
  2. 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), uploads supabase/functions/_shared/whatsappSignupFlow.json, publishes it and saves its id (CommunicationSetting key whatsapp_signup_flow_id). The card then says Published. Pressing it again changes nothing.
  3. 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.
  4. 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%20up and 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

  1. Migration 20261005160000_a_customer_uses_the_whatsapp_menu.sql is applied and whatsapp-webhook and razorpay-order are deployed (the deploy runbook does both).
  2. 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.
  3. For the payment link, the Razorpay secrets RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET must be set (the same ones the app uses). Without them the menu says online payment isn't available and gives the bank details only.
  4. 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.
  5. 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 logs command 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 the WHATSAPP_VERIFY_TOKEN secret.

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.