Chat channel · Live today

WhatsApp Business, on your own infrastructure.

A per-tenant bridge to the Meta WhatsApp Business Cloud API. Your Meta App, your Phone Number, your Access Token — every message and every media blob lands on your own IIS filesystem. No CodeB relay in the middle, no third-party BSP, no per-message markup. HMAC-verified webhooks, appsecret_proof on every Graph call, per-message-id dedup, and a shared “WhatsApp Inbox” group thread so your team replies from the same chat UI they already use.

Open the admin page All chat channels Team Chat
🔒 Per-tenant credentials 🔑 HMAC X-Hub-Signature-256 🛡 appsecret_proof always on 📷 Media cached on your disk 🔁 Per-message-id dedup 🇲🇹 Built in Malta

Included at zero cost with CodeB Sovereign Communications. You pay Meta directly for WhatsApp conversations.

What ships in the box

Everything below is code that lives in /whatsapp.ashx today — no roadmap items, no phase-2 promises. Feature list mirrors what the admin UI can inspect.

Inbound webhook (HMAC-verified)

Meta posts events to /whatsapp.ashx?action=webhook. Every POST is HMAC-SHA256-verified against the tenant’s App Secret in constant time. Forged events with a bad signature get [RASP-WA-WEBHOOK] reject=bad-signature in the diag log and a 401 response.

Outbound send (Graph API v20.0)

POST /whatsapp.ashx?action=send with {to, body}. Posts to graph.facebook.com/v20.0/<phone-number-id>/messages with appsecret_proof appended, so calls survive Meta’s “Require app secret” toggle.

Media caching

Images, audio, video, documents and stickers get fetched from Meta’s lookaside CDN and cached under App_Data/<tenant>/chat-whatsapp/media/<mediaId> with a .meta.json sidecar. Served back via ?action=media&id=<id>, admin-gated. Nothing leaves your infrastructure after the initial Meta fetch.

Per-message dedup

Meta retries webhooks on 5xx or timeout. We use a filename-existence check on messages/wa-<msg-id>.json before writing — Meta message IDs are globally unique, so file-exists is a bulletproof seen-set. Retries log [WA-DEDUP] skip-duplicate instead of double-posting.

Shared “WhatsApp Inbox” group

Every inbound message drops into one shared group thread in chat.html. Your team sees WhatsApp conversations alongside internal DMs and channel messages. No context-switching between apps.

Admin UI

/whatsapp-admin.html ships the config CRUD form, Meta Dashboard values panel with copy buttons, verify-token rotate, test-send widget, webhook subscription reference, and a live activity feed with per-event content preview.

Two-tier admin gate

Config edits and outbound send require either an OIDC Bearer with admin role (validated via loopback to /oidc.ashx?action=userinfo) OR the shared-secret HMAC header. Every rejection logs an [WA-ADMIN-GATE] diag with the client IP.

Robust audit log

Every webhook and every outbound send is written to chat-whatsapp/audit/YYYY-MM-inbound.jsonl / -outbound.jsonl as JSON-per-line. The server pre-extracts a summary object at write time (from, contact, textPreview, statusType) so the admin UI can render meaningful previews without walking deep Meta payload structures.

How the loop works

Round-trip from a customer’s WhatsApp to your team’s chat UI and back — every hop stays on your own infrastructure.

CUSTOMER (WhatsApp mobile) ↓ Meta’s network Meta Cloud API (business.facebook.com) ↓ HTTPS POST + X-Hub-Signature-256 ←— HMAC-SHA256(app_secret, body) /whatsapp.ashx?action=webhook your IIS server ↓ parse + dedup + extract summary chat/groups/wa-inbox/messages/*.json your filesystem ↓ chat.html poll / WS Your team sees the message in the same UI as internal chat ↓ team member replies in the WA Inbox group /whatsapp.ashx?action=send same handler, other direction ↓ POST + appsecret_proof graph.facebook.com/v20.0/*/messages Meta’s network ↓ CUSTOMER (WhatsApp mobile) gets the reply Round-trip typically ~200 ms. Every hop authenticated. No BSP in the middle.

Set it up in six steps

The whole thing takes 15–30 minutes end to end. Longer only if you have to complete Meta’s Business Verification (needed for publishing the app so real inbound flows).

  1. Create a Meta App.On developers.facebook.com/apps create a Business-type app and add the WhatsApp product. Note the App ID and App Secret.
  2. Get a Phone Number ID.WhatsApp Manager → Phone numbers. Use Meta’s assigned Test Number to start, or add your own number (requires Business Verification). Note the Phone number ID.
  3. Get an Access Token.For pilots: temporary 24h token from the API Setup panel. For production: Business Settings → System Users → Add → generate a permanent System User token with whatsapp_business_messaging + whatsapp_business_management permissions.
  4. Open our admin page.Sign in to your tenant and open /whatsapp-admin.html. Paste App ID, App Secret, WABA ID, Phone Number ID, Access Token and Sender Phone. Save.
  5. Configure the Meta webhook.Copy the Callback URL and Verify Token shown on the admin page into Meta App → WhatsApp → Configuration → Webhooks. Click Verify and Save. Subscribe to the messages field.
  6. Send a test message.From the admin page Test Send section, enter an E.164 recipient (must be on Meta’s allowed-tester list until the app is Published) and a body. Send. Reply arrives on the recipient’s WhatsApp within ~200 ms.
Two Meta-side caveats worth flagging up-front. (1) While your Meta App is unpublished / Development, real inbound messages will not flow — only synthetic events fired from Meta’s Send test webhook button. Publishing requires Business Verification. (2) Meta’s Test Number can only send to recipients on the allowed-tester list; adding your own number requires Business Verification too.

vs. a WhatsApp BSP (Twilio, Vonage, MessageBird…)

Business Solution Providers hold the Meta relationship and see every message before forwarding it to you. Our connector is a direct-to-Meta bridge: you register your own Meta App, hold your own Access Token, and Meta posts webhooks straight to your server. Fewer parties, lower latency, no BSP fee.

PropertyTypical BSPCodeB direct-to-Meta bridge
Who holds the Meta relationshipThe BSPYou
Who sees your messages firstThe BSP’s cloudYour own IIS server
Where media is storedBSP CDN + your systemsYour filesystem only, cached on first fetch
Per-message fee on top of Meta’sYes, usually per-messageZero — included with CodeB
Multi-tenant credential isolationBSP-account scopedPer-tenant App_Data directory
Vendor lock-in on message archiveTheir format, their portalPlain JSON files on your disk
Webhook signature verificationHandled by BSP (opaque)HMAC-SHA256 in your code, [RASP-*] audit
appsecret_proof on Graph callsSometimes optionalAlways on, silently backward-compatible
Roll-your-own template managementVia BSP UIMeta Dashboard directly (nothing between you and Meta)

If you need enterprise BSP features (managed template gallery, hosted number provisioning, phone-tree flows), a BSP is the right tool. If you want sovereignty, direct billing, and every byte on your own infrastructure, use this.

Frequently asked

Is this WhatsApp bridge sovereign?

Yes. Each tenant supplies its own Meta App credentials. Every message and every media blob is stored on the tenant’s own IIS filesystem under App_Data. No CodeB-side relay, no third-party BSP in the middle. If you delete the tenant directory, every WhatsApp conversation with that tenant is gone.

How does it differ from Twilio or Vonage’s WhatsApp?

They’re Business Solution Providers — they hold the Meta relationship and see every message before forwarding it. Our connector is a direct-to-Meta bridge: you register your own Meta App, hold your own Access Token, and Meta posts webhooks straight to your server. Fewer parties, lower latency, no per-message BSP fee.

What does “appsecret_proof on every Graph call” mean?

Meta lets app owners require an HMAC-SHA256 proof (App Secret over the Access Token) on every server-to-server Graph API call. Even if an Access Token leaks, calls without the proof are rejected. Our connector always computes and sends the proof, so you can enable Meta’s “Require app secret” toggle any time without breaking anything.

Where does media go?

Inbound message with an image / audio / video / document / sticker: the connector fetches Meta’s media metadata, downloads the binary from the lookaside URL, and caches both under App_Data/<tenant>/chat-whatsapp/media/<mediaId>. Chat clients render inline via /whatsapp.ashx?action=media&id=<mediaId>, admin-gated. Nothing leaves your infrastructure after the initial Meta fetch.

What happens when Meta retries a webhook?

Every inbound message is written to chat/groups/wa-inbox/messages/wa-<msg-id>.json. Meta message IDs are globally unique. On retry the file already exists and the ingestion loop logs [WA-DEDUP] skip-duplicate instead of double-posting.

Can I use this before publishing the Meta App?

Outbound send works pre-publish as long as the recipient is on Meta’s allowed-tester list. Inbound, however, requires the Meta App to be Published (which needs Business Verification). While unpublished, Meta only delivers synthetic events fired from the Meta Dashboard’s Send test webhook button.

How much does it cost?

The connector is included at zero cost with CodeB Sovereign Communications. You pay Meta directly for WhatsApp conversations (see Meta’s WhatsApp Business Pricing page). No per-message CodeB fee, no BSP markup.

Which webhook fields should I subscribe to?

Only messages is required — that covers both inbound messages AND delivery / read / failed status callbacks for outbound messages. Recommended additional subscriptions: message_template_status_update, phone_number_quality_update, account_alerts. Full field list with per-field handling is on the admin page.

Who builds this?

Aloaha Limited in Malta. Same team that builds the rest of CodeB Sovereign Communications.

Ready to plug your Meta App in?

The admin page walks you through every value; the Meta Dashboard values panel shows exactly what to paste where.

Open the admin page All chat channels Team Chat