WhatsApp Cloud API JS

repository·master·Indexed 41 days ago

https://github.com/gokapso/whatsapp-cloud-api-js

A TypeScript client for the WhatsApp Business Cloud API (v0.3.0) featuring typed responses and Zod-validated builders. Supports direct Meta integration and the Kapso proxy for enhanced features like conversation querying, contact management, and faster provisioning. Includes utilities for sending text, media, interactive messages, and WhatsApp Flows, as well as tools for template management and webhook verification.

Tokens
19K
Snippets
57
Records
77
Agent score
53%

What's inside WhatsApp Cloud API JS

  1. Query history and contacts via Kapso Proxy

    master

    When using the Kapso proxy (baseUrl: "https://api.kapso.ai/meta/whatsapp"), the client can query stored historical data.

    Available Resources:

    • Conversations: List, get, and update status (e.g., ended).
    • Messages: Query history with filters like direction, since, and limit. Supports Graph paging via cursors.
    • Contacts: List and update contact metadata (e.g., tags).
    • Calls: List and get call logs.

    Data Hydration: To include Kapso-specific extensions in your results, use the fields parameter with buildKapsoFields() or the string "kapso(default)".

    • fields: "kapso(default)": Includes all Kapso extensions.
    • fields: "kapso(content)": Hydrates normalized messages with original payload fragments.
    • fields: "kapso()": Omits Kapso extras entirely.
    import { buildKapsoFields } from '@kapso/whatsapp-cloud-api';
    
    const client = new WhatsAppClient({
      baseUrl: "https://api.kapso.ai/meta/whatsapp",
      kapsoApiKey: process.env.KAPSO_API_KEY!,
    });
    
    // Querying message history
    const history = await client.messages.query({
      phoneNumberId: "647015955153740",
      direction: "inbound",
      since: "2025-01-01T00:00:00Z",
      limit: 50,
      fields: buildKapsoFields(),
    });
  2. Understand Business-scoped user IDs (BSUIDs)

    master

    Meta is transitioning from phone numbers to Business-scoped user IDs (US.13491208655302741918) for user identification.

    When sending messages, you can provide a recipient (the BSUID) instead of to (the phone number). If both are provided, Meta delivers to the phone number. It is recommended to provide both to ensure reliable delivery as users move toward usernames.

    Note: Certain authentication templates (one-tap, zero-tap, copy-code) still require a phone number and cannot be delivered to a BSUID (Error 131062).

    // Recommended: Provide both identifiers
    await client.messages.sendText({
      phoneNumberId: "<PHONE_NUMBER_ID>",
      to: "+15551234567",
      recipient: "US.13491208655302741918",
      body: "On its way.",
    });
  3. Handle Media uploads and downloads

    master

    The SDK supports uploading, retrieving, and downloading media.

    Uploading Media Pass a Blob or similar file object to client.media.upload.

    Downloading Media Use client.media.download({ mediaId, ... }). This method is intelligent:

    • It resolves short-lived URLs via media.get() automatically.
    • It skips auth headers for public WhatsApp CDNs but uses them for Kapso hosts.
    • You can specify the return type using the as option: "blob" or "response". Defaults to ArrayBuffer.

    Receiving Media (Kapso Optimization) If using Kapso, you can request kapso(media_url) when listing messages. This allows you to render the URL directly without a separate download step.

    // Downloading bytes from a message record
    const { data } = await client.messages.query({ phoneNumberId: "<PHONE_NUMBER_ID>", limit: 1 });
    const msg = data[0];
    
    if (msg.type === "image" && msg.image?.id) {
      const mediaId = msg.image.id;
      const bytes = await client.media.download({ 
        mediaId, 
        phoneNumberId: "<PHONE_NUMBER_ID>" 
      });
      // bytes is an ArrayBuffer
    }
  4. Handle and verify Webhooks

    master

    To process incoming WhatsApp events, use verifySignature to validate the request from Meta and normalizeWebhook to transform the raw Graph API payload into a consistent, camelCased format.

    normalizeWebhook() unwraps the payload into an object containing { messages, statuses, calls, contacts }. This ensures that webhook events share the same structure as history queries.

    Key features of normalized events:

    • Directionality: Messages include kapso.direction ("inbound" or "outbound").
    • SMB Echoes: Messages initiated by the business are tagged with kapso.source = "smb_message_echo".
    • Raw Access: Any other Meta field payloads are available under events.raw.<fieldName> (camelCased), allowing you to react to accountAlerts, templateCategoryUpdate, etc.
    • Contacts: events.contacts contains the contact array with camelCased fields.
    import express from "express";
    import { normalizeWebhook, verifySignature } from "@kapso/whatsapp-cloud-api/server";
    
    app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
      const ok = verifySignature({
        appSecret: process.env.META_APP_SECRET!,
        rawBody: req.body,
        signatureHeader: req.headers["x-hub-signature-256"] as string,
      });
      if (!ok) return res.status(401).end();
    
      const payload = JSON.parse(req.body.toString("utf8"));
      const events = normalizeWebhook(payload);
    
      events.messages.forEach((message) => {
        // message matches the same shape returned by client.messages.query()
      });
    
      events.statuses.forEach((status) => {
        // handle delivery receipts
      });
    
      events.calls.forEach((call) => {
        // handle calling events
      });
    
      res.sendStatus(200);
    });
  5. Manage WhatsApp Templates

    master

    The SDK provides methods to create, retrieve, and send templates.

    1. Create a Template Use a definition built via buildTemplateDefinition (or TemplateDefinition.buildTemplateDefinition) and pass it to client.templates.create.

    2. Get a Template Retrieve template details using client.templates.get({ businessAccountId, templateId }).

    3. Send a Template Construct a payload with buildTemplateSendPayload and send it via client.messages.sendTemplate.

    import { buildTemplateSendPayload } from "@kapso/whatsapp-cloud-api";
    
    const templatePayload = buildTemplateSendPayload({
      name: "seasonal_promo",
      language: "en_US",
      header: { type: "image", image: { link: "https://cdn.example/banner.jpg" } },
      body: [ { type: "text", text: "Aug 31" }, { type: "text", text: "SALE25" } ],
      buttons: [ { type: "button", subType: "quick_reply", index: 0, parameters: [{ type: "payload", payload: "STOP" }] } ],
    });
    
    await client.messages.sendTemplate({
      phoneNumberId: "<PHONE_NUMBER_ID>",
      to: "+15551234567",
      template: templatePayload,
    });
  6. Checklist for Production WhatsApp Flows

    master

    Before deploying WhatsApp Flows to production, ensure you meet these requirements:

    • Casing: All Flow JSON must be authored in camelCase (e.g., dataApiVersion, routingModel, onClickAction).
    • Performance: The Data Endpoint must respond within 10 seconds to avoid Meta timeouts.
    • Error Handling: Handle specific FlowServerError statuses:
      • 421: Decryption error
      • 432: Signature/HMAC error
      • 427: Invalid flow token
    • Security: Refresh the Flow private key if you rotate your phone numbers.
    • Observability: Log ctx.action, ctx.screen, and request timing for monitoring.
  7. Deploy and send WhatsApp Flows

    master

    WhatsApp Flows allow you to create interactive, multi-screen experiences. Use client.flows.deploy() for idempotent deployments of a Flow JSON definition. To send a Flow to a user, use client.messages.sendInteractiveFlow.

    // Deploy a Flow
    await client.flows.deploy(flowJson, {
      wabaId: process.env.WABA_ID!,
      name: "csat-flow",
      publish: true,
      preview: true
    });
    
    // Send a Flow message
    await client.messages.sendInteractiveFlow({
      phoneNumberId: "1234567890",
      to: "+15551234567",
      bodyText: "Check out our new experience",
      parameters: {
        flowId: "1234567890",
        flowCta: "Open",
        mode: "draft", // Use "draft" for unpublished Flows
        flowToken: "token123",
        flowAction: "navigate",
        flowActionPayload: { screen: "WELCOME" }
      }
    });
  8. Quick start with WhatsAppClient

    master

    Initialize a WhatsAppClient using a Meta access token. You can also route requests through the Kapso proxy by providing a baseUrl and kapsoApiKey.

    import { WhatsAppClient } from "@kapso/whatsapp-cloud-api";
    
    const client = new WhatsAppClient({
      accessToken: process.env.WHATSAPP_TOKEN!,
      // or route via Kapso proxy:
      // baseUrl: "https://api.kapso.ai/meta/whatsapp",
      // kapsoApiKey: process.env.KAPSO_API_KEY,
    });
    
    await client.messages.sendText({
      phoneNumberId: "<PHONE_NUMBER_ID>",
      to: "+15551234567",
      body: "Hello from Kapso",
    });
  9. Configure the Kapso Proxy setup

    master

    To use the Kapso proxy instead of a direct Meta setup, provide the baseUrl and kapsoApiKey in the WhatsAppClient constructor. This setup allows for faster provisioning (~2 minutes) and access to extra features like conversation querying and contact management.

    const client = new WhatsAppClient({
      baseUrl: "https://api.kapso.ai/meta/whatsapp",
      kapsoApiKey: process.env.KAPSO_API_KEY!,
    });
  10. Deploy and preview WhatsApp Flows

    master

    Use client.flows.deploy to upload your Flow JSON. This method is idempotent: it skips asset uploads if the content hash has not changed. You can optionally publish the flow or request a preview URL.

    Options for deploy:

    • wabaId: Your WhatsApp Business Account ID.
    • name: The name of the flow.
    • publish: Boolean to publish the flow immediately.
    • preview: Boolean to get a preview URL, or an object for interactive previews: { interactive: true, params: { flowAction: "navigate" } }.
    import { WhatsAppClient } from "@kapso/whatsapp-cloud-api";
    import csatFlow from "./flows/csat.flow";
    
    const client = new WhatsAppClient({ accessToken: process.env.WHATSAPP_TOKEN! });
    
    const result = await client.flows.deploy(csatFlow, {
      wabaId: process.env.WABA_ID!,
      name: "csat-flow",
      publish: true,
      preview: true, // or { interactive: true, params: { flowAction: "navigate" } }
    });
    
    console.log("Flow ID:", result.flowId);
    console.log("Preview URL:", result.previewUrl);
    console.log("Validation errors:", result.validationErrors);
  11. Author WhatsApp Flows using camelCase

    master

    The SDK allows you to author Flow JSON using idiomatic TypeScript/JavaScript camelCase. The SDK automatically converts these keys to Meta's required kebab-case (e.g., onClickAction becomes on-click-action) during upload. This reduces errors and improves developer experience.

    Key benefits:

    • CamelCase authoring: Write JSON naturally; the SDK handles the mapping.
    • DX-focused validation: If you make a casing mistake, the SDK surfaces Meta's errors with camelized pointers and helpful hints.
    • Lifecycle helpers: Methods like create, updateAsset, and deploy mirror the Graph API but work with your camelCase objects.
    export default {
      version: "7.2",
      screens: [
        {
          id: "CSAT",
          title: "How was your experience?",
          terminal: true,
          layout: {
            type: "SingleColumnLayout",
            children: [
              {
                type: "RadioButtonsGroup",
                name: "rating",
                label: "Rate us",
                required: true,
                dataSource: [
                  { id: "up", title: "👍 Great" },
                  { id: "down", title: "👎 Not great" }
                ]
              },
              {
                type: "Footer",
                label: "Submit",
                onClickAction: {
                  name: "complete",
                  payload: { rating: "${form.rating}" }
                }
              }
            ]
          }
        }
      ]
    } as const;