DEV Community

Cover image for WhatsApp's URL Architecture: The Distributed GraphQL Mesh
Black Lover
Black Lover

Posted on

WhatsApp's URL Architecture: The Distributed GraphQL Mesh

Source: Decompiled WhatsApp Android version 2.26.3.79. All endpoints, operation names, and URL patterns are as observed in the client binary.

WhatsApp doesn't use a single GraphQL endpoint. It operates a distributed mesh of specialized GraphQL gateways, each serving a distinct domain: payments, generative AI, Oculus/AR, zero-rating, and core messaging. This federated graph architecture enables independent scaling, regional deployment, and security isolation between domains that have no business touching each other's data.

This is a deep-dive into how that mesh is structured, how mutations are routed across it, and what the URL patterns reveal about Meta's broader infrastructure strategy.


The Three-Schema Foundation

Before getting into endpoints, it helps to understand the schema layer they serve:

Schema Owner Purpose
whatsapp WhatsApp team Core messaging, AI features, payments, maps
whatsapp_mex MEX platform team High-trust: DMA interop, parental controls, passkeys, identity
facebook Meta platform team Cross-Meta: ads, commerce, digital content

Every GraphQL operation in the client is tagged to one of these schemas. That tag determines which master endpoint receives the request and which slave services it can fan out to. The schema is the routing key.


1. The GraphQL Endpoint Mesh

Complete Endpoint Inventory

// Core GraphQL endpoints (from decompiled client)
X_GRAPH_FACEBOOK_GRAPHQL_URL               // https://graph-www.facebook.com/graphql
X_GRAPH_FACEBOOK_ZERO_RATING_BOOTSTRAP_URL // https://b-graph.facebook.com/graphql
X_GRAPH_OCULUS_GRAPHQL_URL                 // https://graph.oculus.com/graphql
X_GRAPH_PAYMENTS_GRAPHQL_URL               // https://payments-graph.facebook.com/graphql
X_GRAPH_FACEBOOK_GENAI_GRAPHQL_URL         // https://genai-graph.facebook.com/graphql
X_GRAPH_INSTAGRAM_GENAI_GRAPHQL_URL        // https://genai-graph.instagram.com/graphql_www
X_GRAPH_INSTAGRAM_PAYMENTS_GRAPHQL_URL     // https://payments-graph.instagram.com/graphql_www

// WhatsApp-specific endpoints
https://graph.whatsapp.net/v2.2/maps_configs
https://graph.whatsapp.net/wa_qpl_data
https://static.whatsapp.net/wa/static/network_resource
https://static.whatsapp.net/sticker?img=
https://wa.me/stickerpack/%s
https://chat.whatsapp.com/

// Third-party integrations
https://api.giphy.com/v1/stickers/search      // Giphy stickers (pg-13 filtered)
https://scontent.oculuscdn.com/               // Oculus/Meta VR content
https://lookaside.facebook.com/assets/key/    // Meta design system assets
Enter fullscreen mode Exit fullscreen mode

Endpoint Purpose Map

Endpoint Purpose Schema
graph-www.facebook.com Core Facebook graph facebook
b-graph.facebook.com Zero-rated bootstrap (emergency access) Subset of core
graph.oculus.com VR/AR metadata, smart glasses Oculus schema
payments-graph.facebook.com Payment processing facebook with payment scope
genai-graph.facebook.com Generative AI (Imagine, MEmu) GenAI schema
genai-graph.instagram.com Instagram AI features Cross-platform AI
payments-graph.instagram.com Instagram payments Instagram commerce
graph.whatsapp.net Core WhatsApp messaging whatsapp / whatsapp_mex

This is not a monolith. Each endpoint is owned by a different team, scales independently, has separate authentication requirements, and — critically — separate data access boundaries. The GenAI endpoint has no access to your contact list. The payments endpoint has no access to your message history. That isolation is the point.

Why Federated GraphQL Over a Monolith?

The conventional alternative — a single GraphQL endpoint with a unified schema — breaks down at Meta's scale for several reasons:

Independent deployability. If the GenAI service needs a schema change, it doesn't require a coordinated deploy across the entire messaging infrastructure. Each team ships on their own cadence.

Security isolation. A SQL injection or logic flaw in the Giphy proxy can't escalate to contact list access because those services literally don't share a schema or auth context.

Regional compliance. EU data residency requirements can be enforced at the endpoint level — EU users' MEX operations route to Frankfurt infrastructure, never leaving the region. A monolith makes this significantly harder.

Failure containment. If genai-graph.facebook.com goes down during a viral "imagine me" moment, core messaging at graph.whatsapp.net is unaffected. The master-slave fan-out pattern provides graceful degradation.


2. The Master-Slave Mutation Architecture

Operation Hashing: The Core Pattern

Every GraphQL operation in the WhatsApp client is represented by a triple-hash system rather than inline query text:

{
  "AddMEmuProfilePhotos": {
    "operation_name": "AddMEmuProfilePhotos",
    "operation_name_hash": "793005150",
    "operation_text_hash": "6669169098671059140",
    "client_doc_id": "7930051506669169098671059140",
    "schema": "whatsapp"
  }
}
Enter fullscreen mode Exit fullscreen mode

Three hashes, three purposes:

Hash Purpose
operation_name_hash Route to correct service / operation registry
operation_text_hash Look up persisted query text on the server
client_doc_id Composite cache/CDN key (name hash + text hash concatenated)

This is the persisted queries pattern: the full GraphQL query text is never sent over the network. The client sends only the doc_id and variables. The server looks up the query text by hash from a pre-registered store and executes it.

Why this matters:

  • Bandwidth savings — A complex mutation with nested fragments might be 2–3KB of query text. Sending a 26-character hash instead adds up across 2.5 billion daily users.
  • Security — Arbitrary query execution is impossible. The server will only run pre-registered, pre-validated queries. This eliminates an entire category of GraphQL abuse (deeply nested queries, field enumeration, introspection attacks).
  • Build-time validation — Operations are validated against the schema at build time. Type mismatches and missing fields are caught before the APK ships, not at runtime.
  • Analytics — Every operation is identified by a stable hash, making performance tracking and error attribution trivial even across client versions.

The Wire Protocol

What the client actually sends:

POST https://graph.whatsapp.net/graphql
{
  "doc_id": "7930051506669169098671059140",
  "variables": {
    "photos": ["base64_photo_1", "base64_photo_2"],
    "profile_id": "123456789"
  }
}
Enter fullscreen mode Exit fullscreen mode

What the server resolves and executes:

mutation AddMEmuProfilePhotos($photos: [String!]!, $profile_id: ID!) {
  addMEmuProfilePhotos(photos: $photos, profile_id: $profile_id) {
    success
    profile {
      id
      photo_count
      preview_url
    }
    errors {
      code
      message
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The query text never touches the network. The client binary contains hashes; the server holds the query registry.

Master-Slave Routing

The schema field in each operation manifest determines which GraphQL master handles the request:

schema: "whatsapp"     → graph.whatsapp.net
schema: "whatsapp_mex" → Internal MEX gateway (higher trust, separate auth)
schema: "facebook"     → graph-www.facebook.com
Enter fullscreen mode Exit fullscreen mode

Each master can then fan out to specialized slave services:

graph.whatsapp.net (whatsapp master)
├── genai-graph.facebook.com    (AI image generation)
├── payments-graph.facebook.com (UPI/Pix processing)
└── graph.oculus.com            (AR/smart glasses)

MEX gateway (whatsapp_mex master)
├── Compliance services         (DMA enforcement, audit logging)
├── PAA services                (parental supervision)
└── Monetization services       (Wamo/channel subscriptions)

graph-www.facebook.com (facebook master)
└── Ads services                (Meta Ads, business tools)
Enter fullscreen mode Exit fullscreen mode

Full Operation Routing Table

Operation Prefix Schema Master Endpoint Slave Endpoints
GenAI*, Imagine*, MEmu* whatsapp graph.whatsapp.net genai-graph.facebook.com, genai-graph.instagram.com
*Payment*, *Upi*, *Pix* whatsapp graph.whatsapp.net payments-graph.facebook.com
*Interop*, *Paa*, *Passkey* whatsapp_mex Internal MEX gateway Compliance services
*Newsletter*, *Wamo* whatsapp_mex Internal MEX gateway Monetization services
*Ad*, *Commerce*, *DigitalContent* facebook graph-www.facebook.com Ads services
*Oculus*, *AR* whatsapp graph.whatsapp.net graph.oculus.com

3. Sample Mutations by Domain

A. Generative AI (MEmu / Imagine)

# Create AI avatar profile from user photos
mutation CreateMEmuProfile($photos: [String!]!, $name: String) {
  createMEmuProfile(photos: $photos, name: $name) {
    profile_id
    status
    preview_url
    estimated_completion_seconds
  }
}

# Generate image from text prompt
mutation GenAIImagineGenerateMutation($prompt: String!, $style: ImagineStyle) {
  genAIImagineGenerate(prompt: $prompt, style: $style) {
    generation_id
    image_url
    thumbnail_url
    seed
    prompt_embedding
  }
}

# Animate a static image
mutation GenAIEditAnimateMutation($image_id: ID!, $animation_style: AnimationStyle) {
  genAIEditAnimate(image_id: $image_id, animation_style: $animation_style) {
    video_url
    duration_ms
    frames
  }
}

# Bulk send AI-generated media to a chat
mutation GenAIImagineBulkSendMediaToChat($media_ids: [ID!]!, $chat_jid: String!) {
  genAIImagineBulkSendMediaToChat(media_ids: $media_ids, chat_jid: $chat_jid) {
    success_count
    failed_ids
    message_id
  }
}
Enter fullscreen mode Exit fullscreen mode

Note prompt_embedding in the Imagine response — the server returns the semantic embedding of the prompt, not just the image. This is likely used for client-side related prompt suggestions and "try similar" features without requiring another round-trip.

B. Payments (UPI / Pix)

# Generate payment key for UPI transaction
mutation GenCreatePaymentKey(
  $amount: Int!,
  $currency: String!,
  $receiver_vpa: String!
) {
  genCreatePaymentKey(
    amount: $amount,
    currency: $currency,
    receiver_vpa: $receiver_vpa
  ) {
    payment_key
    expiry_seconds
    upi_qr_code_url
  }
}

# Complete a Pix transaction (Brazil)
mutation CompletePixTransaction($transaction_id: ID!, $pix_key: String!) {
  completePixTransaction(
    transaction_id: $transaction_id,
    pix_key: $pix_key
  ) {
    status
    receipt_url
    confirmation_code
    error_code
  }
}

# UPI onboarding OTP
mutation UpiOnboardingSendOtpMutation($phone_number: String!, $bank_code: String) {
  upiOnboardingSendOtp(phone_number: $phone_number, bank_code: $bank_code) {
    otp_ref_id
    resend_timeout_seconds
    masked_phone
  }
}
Enter fullscreen mode Exit fullscreen mode

The separation of GenCreatePaymentKey (which talks to UPI rails) from CompletePixTransaction (Brazil's Pix system) reflects the reality that WhatsApp Payments is deployed differently by region. India uses UPI with NPCI settlement; Brazil uses Pix with Banco Central do Brasil oversight. These are distinct regulatory environments with distinct backend integrations, and the mutation structure reflects that.

C. MEX Operations (DMA / Parental / Passkeys)

# Create interoperable group with third-party platform users
mutation GroupsCreateInteropGroup(
  $name: String!,
  $participants: [InteropParticipantInput!]!
) {
  groupsCreateInteropGroup(name: $name, participants: $participants) {
    group_id
    invite_link
    external_platform_mappings {
      platform
      external_id
      invite_url
    }
  }
}

# Child accepts parental supervision
mutation PaaAcceptLinkingMutation($supervisor_id: ID!, $pin: String!) {
  paaAcceptLinking(supervisor_id: $supervisor_id, pin: $pin) {
    status
    supervision_level
    activity_sharing_enabled
    next_review_date
  }
}

# Complete FIDO2 passkey registration
mutation RegistrationPasskeyFinishRegisterMutation(
  $challenge: String!,
  $attestation: String!
) {
  registrationPasskeyFinishRegister(
    challenge: $challenge,
    attestation: $attestation
  ) {
    success
    passkey_id
    backup_eligible
  }
}
Enter fullscreen mode Exit fullscreen mode

The backup_eligible field in the passkey response is significant — it maps to the FIDO2 concept of a "multi-device credential" (a passkey synced via iCloud Keychain or Google Password Manager) vs. a "single-device credential" (hardware-bound, not synced). WhatsApp is tracking this distinction per passkey, which matters for account recovery: if your only passkey is hardware-bound and you lose the device, there's no backup path.

D. Newsletter / Channel Mutations

# Create a verified channel
mutation NewsletterCreateVerified(
  $name: String!,
  $description: String,
  $verification_docs: [String!]
) {
  newsletterCreateVerified(
    name: $name,
    description: $description,
    verification_docs: $verification_docs
  ) {
    newsletter_id
    vanity_url
    verification_status
    admin_ids
  }
}

# Enable paid subscription (Wamo)
mutation NewsletterEnableWamoSub(
  $newsletter_id: ID!,
  $monthly_price: Int!,
  $currency: String!
) {
  newsletterEnableWamoSub(
    newsletter_id: $newsletter_id,
    monthly_price: $monthly_price,
    currency: $currency
  ) {
    subscription_tier
    payout_account_status
    subscriber_count
  }
}
Enter fullscreen mode Exit fullscreen mode

payout_account_status confirms that creator payouts are a first-class concern in the data model — not a post-hoc integration with an external payments provider, but something WhatsApp tracks directly. Combined with the currency field accepting multiple values, this suggests Wamo payouts will support multiple currencies from launch, relevant for WhatsApp's largest markets (India, Brazil, Nigeria, Indonesia).


4. The QPL Telemetry Pipeline

HWQ hwq = new HWQ(..., "https://graph.whatsapp.net/wa_qpl_data", ...);
hwq.A08("access_token", "1063127757113399|745146ffa34413f9dbb5469f5370b7af");
Enter fullscreen mode Exit fullscreen mode

QPL = Query Performance Logger — Meta's internal distributed tracing system. Every GraphQL operation, database query, and UI render sends performance telemetry to this endpoint.

The hardcoded token is a public app token scoped exclusively to QPL data ingestion:

  • App ID 1063127757113399 = WhatsApp QPL application
  • Scope is write-only to wa_qpl_data — it cannot read user data, contact lists, or message content
  • Exposing it in the binary is intentional — it's the equivalent of a public write-only API key for a logging endpoint

The QPL Mutation

mutation LogPerformanceTrace(
  $trace_id: ID!,
  $operation_name: String!,
  $duration_ms: Int!,
  $cache_hit: Boolean!,
  $error_code: Int
) {
  logPerformanceTrace(
    trace_id: $trace_id,
    operation_name: $operation_name,
    duration_ms: $duration_ms,
    cache_hit: $cache_hit,
    error_code: $error_code
  ) {
    accepted
    sampling_rate
  }
}
Enter fullscreen mode Exit fullscreen mode

Note the sampling_rate in the response — the server tells the client what percentage of subsequent traces to send. This is dynamic sampling: during low-traffic periods, sample 100% of traces; during peak load, drop to 1% to avoid the telemetry pipeline itself becoming a bottleneck. The client adjusts its reporting rate in real time based on server guidance.

This telemetry infrastructure allows Meta engineers to:

  • Detect slow GraphQL resolvers (p95 latency by operation name)
  • Optimize cache hit rates per operation
  • Identify error spikes before they surface in user complaints
  • A/B test infrastructure changes with statistical confidence
  • Correlate client-side duration with server-side resolver timing

For a 2.5 billion user app, even 1% sampling gives millions of data points per hour. The QPL pipeline is how Meta knows when something is slow before users notice.


5. Third-Party Integrations

Giphy Sticker Search

public String A03(CharSequence charSequence, String str) {
    String[] strArr = new String[10];
    strArr[0] = "api_key";
    strArr[1] = AbstractC11310fq.A0M;  // Giphy API key
    strArr[2] = "q";
    strArr[4] = "lang";
    strArr[6] = "rating";
    strArr[7] = "pg-13";               // Hardcoded content filter
    strArr[8] = "limit";
    strArr[9] = "100";
    return FIO.A00("https://api.giphy.com/v1/stickers/search", strArr);
}
Enter fullscreen mode Exit fullscreen mode

The pg-13 rating filter is hardcoded, not configurable. This means adult content is filtered at the API call level regardless of user age or settings — a deliberate product decision, not a Giphy default. WhatsApp is a family communication app in most of its core markets; this is a conscious content policy choice baked into the client binary.

The architecture here is a direct proxy: WhatsApp calls Giphy from the client, not through a Meta backend. Giphy data never enters Meta's GraphQL infrastructure. This keeps Giphy content legally and architecturally separate from Meta's data processing — relevant for EU data transfer regulations.

User search query → WhatsApp client → Giphy API directly → Results returned to client
                                     (Meta servers never see Giphy content)
Enter fullscreen mode Exit fullscreen mode

Oculus CDN Asset Structure

https://scontent.oculuscdn.com/v/t64.5771-25/499303769_711556704750162_3777318892782513230_n.mp4
  ?_nc_cat=105        → CDN category (105 = video asset)
  &ccb=1-7            → Cache control bucket
  &_nc_sid=5f5f54     → Session ID for analytics
  &_nc_ohc=...        → Oculus content hash (content-addressed)
  &_nc_oc=...         → Origin content ID
  &_nc_zt=28          → Zero-trust security token
  &_nc_ht=scontent.oculuscdn.com
  &oh=00_Afn4...      → Origin hash (integrity check)
  &oe=69457811        → Origin expiration (Unix timestamp)
Enter fullscreen mode Exit fullscreen mode

The _nc_zt (zero-trust token) and oh (origin hash) parameters together implement signed URL access control — the URL is only valid for a specific client session for a limited time window (oe = expiry). Sharing or scraping the URL without a valid session token returns 403. This is standard CDN security for premium content but interesting to see applied to onboarding/tutorial videos.

The content (t64.5771-25 type prefix) is likely a smart glasses onboarding video or AR effect tutorial served to WhatsApp clients supporting Meta's Ray-Ban Stories / Orion integration.


6. Static Asset Architecture

WhatsApp uses purpose-built static domains for different asset types:

// Sticker images (individual)
"https://static.whatsapp.net/sticker?img="

// Sticker packs (bulk)
"https://wa.me/stickerpack/%s"

// Network resources (animations, success states)
"https://static.whatsapp.net/wa/static/network_resource
  ?cat=nw_media&id=username_success_confetti_tall_green"

// Group invite links
"https://chat.whatsapp.com/"

// Meta design system assets (shared across all Meta apps)
"https://lookaside.facebook.com/assets/key/meta_brand_design_system_icons_raster"
Enter fullscreen mode Exit fullscreen mode

lookaside.facebook.com is Meta's global asset delivery network for design system assets — icons, color tokens, raster graphics — shared across WhatsApp, Instagram, Facebook, and Messenger. When Meta updates an icon in their design system, it propagates to all four apps via a CDN cache invalidation rather than four separate app releases. This enables consistent visual branding across the Meta family without requiring coordinated app updates.

The network_resource endpoint for animations (confetti, success states) is particularly efficient — these are not bundled in the APK but fetched on demand and cached. The APK stays smaller; the CDN handles distribution of infrequently-used animation assets.


7. Map Infrastructure

static {
    CCR ccr = new CCR(
        "https://www.facebook.com/maps/tile/?",    // Tile server
        "https://www.facebook.com/maps/static/?",  // Static map images
        null, null, null, Integer.MAX_VALUE
    );
    A09 = ccr;  // Facebook maps config

    A0A = new CCR(
        "https://maps.instagram.com/maps/tile/?",
        "https://maps.instagram.com/maps/static/?",
        null, null, null, Integer.MAX_VALUE
    );  // Instagram maps config
}
Enter fullscreen mode Exit fullscreen mode

The map configuration is fetched dynamically:

GET https://graph.whatsapp.net/v2.2/maps_configs
  ?fields=base_url,static_base_url,osm_config,url_override_config
  &pretty=0
  &access_token=TOKEN
Enter fullscreen mode Exit fullscreen mode

Key architectural insight: the tile server URL is fetched from the server, not hardcoded. The url_override_config field allows Meta to hot-switch the tile provider (from Facebook's servers to a third-party or regional provider) without an app update. This is useful for regional compliance — in markets where Facebook infrastructure is blocked or restricted, the tile server can be redirected to a neutral CDN.

WhatsApp uses OpenStreetMap (osm_config) for map data — community-maintained, license-compatible, and not subject to Google Maps API pricing. Facebook and Instagram serve as tile renderers on top of OSM data, enabling:

  • Location sharing with live map previews
  • Business location display in profiles
  • Event location maps
  • Location-based AR effects

8. End-to-End Flow: "Imagine Me"

Putting it all together with a concrete example. When a user sends "imagine me as an astronaut" to Meta AI in WhatsApp:

1. Client hashes operation
   CreateMEmuProfile → doc_id: "1941315421..."

2. Client sends to whatsapp master
   POST graph.whatsapp.net/graphql
   { doc_id: "1941315421...", variables: { photos: [...], prompt: "astronaut" } }

3. WhatsApp master validates
   - Auth token verification
   - Rate limit check (per-user, per-operation)
   - Persisted query lookup by doc_id

4. Master routes to GenAI slave
   POST genai-graph.facebook.com/graphql
   (with scoped auth token, no contact list access)

5. GenAI slave processes
   - Photo embedding generation
   - Diffusion model inference
   - Result CDN upload

6. Response propagates back
   genai slave → whatsapp master → client

7. QPL telemetry fires
   POST graph.whatsapp.net/wa_qpl_data
   { operation: "CreateMEmuProfile", duration_ms: 4200, cache_hit: false }
Enter fullscreen mode Exit fullscreen mode

Total round-trips: 2 (mutation + telemetry). The GenAI slave's data access is cryptographically scoped — it processes photos for the AI task but has no API access to the user's contacts, messages, or other profile data.


9. Security Architecture

Token Scoping (Principle of Least Privilege)

The QPL token demonstrates Meta's token scoping model:

1063127757113399|745146ffa34413f9dbb5469f5370b7af
└── App ID ────┘└── App secret (write-only, QPL scope) ─┘
Enter fullscreen mode Exit fullscreen mode
  • Can write to wa_qpl_data only
  • Cannot read any user data
  • Cannot execute GraphQL mutations outside QPL schema
  • Public exposure is intentional — it's a write-only logging key

Each slave service receives a scoped token from the master with only the permissions needed for that operation. The GenAI slave token cannot query the payments service. The payments token cannot access the contact graph. The MEX gateway uses a higher-trust token with audit logging enabled.

Zero-Rating Bootstrap

b-graph.facebook.com is a zero-rated endpoint — accessible even when the user has no mobile data balance. Operators in participating markets (primarily in South and Southeast Asia, sub-Saharan Africa) have agreements with Meta to not charge data for this subdomain.

The endpoint exposes only a strict subset of the full API:

  • Emergency registration and SIM verification
  • Critical security updates and key rotation
  • Basic message queuing (not full messaging)
  • Account recovery flows

This prevents abuse (zero-rated endpoints are high-value targets for data scraping) while ensuring essential functionality remains accessible to users on prepaid plans. The restricted schema means even if someone discovers the zero-rated endpoint, they can't use it to access arbitrary API functionality for free.

Persisted Queries as a Security Control

The operation hashing system is also a security mechanism. Because the server only executes pre-registered queries:

  • No introspection attacks — you can't query __schema to enumerate available fields
  • No batch abuse — you can't craft a custom query that joins 10 expensive resolvers
  • No field enumeration — you can't discover undocumented fields by probing the schema
  • Audit trail — every executed query is identifiable by a stable hash in logs

This is a meaningful security posture for an app handling 100+ billion messages per day.


Summary

WhatsApp's URL and GraphQL architecture reveals a federated master-slave system built around three core principles:

Security isolation over convenience. Separate endpoints, separate auth tokens, separate data scopes. The GenAI service literally cannot access your contact list, by architecture, not just by policy.

Operational independence. Each domain (AI, payments, messaging, compliance) deploys, scales, and fails independently. A viral "imagine me" moment doesn't degrade core messaging performance.

Regulatory partitioning. The MEX gateway, the zero-rating bootstrap, the scoped token model — all of these are designed for a world where different operations face different regulatory requirements in different jurisdictions. The architecture makes compliance enforceable at the infrastructure level, not just the policy level.

For an app serving 2.5 billion users across radically different regulatory environments — EU DMA, India UPI regulations, Brazil Pix, US compliance requirements — this kind of federated, domain-isolated architecture isn't over-engineering. It's the minimum viable structure for operating at that scale and that regulatory complexity simultaneously.


Analysis based on decompiled GraphQL operation manifests and URL patterns from WhatsApp Android 2.26.3.79.

Top comments (0)