Pinecall

WhatsApp

Build a WhatsApp messaging agent using Meta's Cloud API.

Server-side LLM required. WhatsApp channels use the same llm config as voice channels. Client-side LLM (bring-your-own) is not supported for WhatsApp.

Setup#

1. Create a Meta Business App#

Go to developers.facebook.com and create a Business app.

2. Add the WhatsApp product#

In your app dashboard, add WhatsApp from the product catalog.

3. Collect your credentials#

From the WhatsApp → API Setup page, grab:

  • Phone Number ID — numeric string (e.g. 123456789012345)
  • Permanent Access Token — generate a system user token with whatsapp_business_messaging permission
  • App Secret — from App Settings → Basic (used for webhook signature verification)

4. Configure the webhook in your Meta app#

Webhook URL:

https://voice.pinecall.io/whatsapp/webhook

Verification token: must match the verifyToken you pass to the SDK (default: pinecall-wa-verify).

Subscribe to the messages field.

Minimal WhatsApp agent#

import { Pinecall } from "@pinecall/sdk";

const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
await pc.connect();

const support = pc.agent("support", {
  language: "en",
  llm: {
    provider: "openai",
    model: "gpt-4.1-mini",
    enabled: true,
    prompt: "You are a helpful support agent on WhatsApp. Be concise.",
  },
});

support.addChannel("whatsapp", {
  phoneNumberId: "123456789012345",
  accessToken: process.env.WA_TOKEN!,
  verifyToken: "my-verify-token",
  appSecret: process.env.WA_APP_SECRET!,
});

support.on("whatsapp.message", (event) => {
  console.log(`📩 ${event.name}: ${event.text}`);
});

That's it. The first message a new contact sends fires whatsapp.session_started, the LLM generates a response, and the SDK sends it back via the Cloud API.

WhatsAppChannelConfig#

FieldTypeRequiredDescription
phoneNumberIdstringMeta Phone Number ID from API Setup
accessTokenstringPermanent Graph API access token
verifyTokenstringWebhook verification token (default: pinecall-wa-verify)
appSecretstringApp secret for HMAC signature verification (strongly recommended)

Adding tools#

Tools work identically on WhatsApp and voice channels. The same llm.tool_call handler covers both:

const support = pc.agent("support", {
  llm: {
    provider: "openai",
    model: "gpt-4.1-mini",
    enabled: true,
    prompt: "...",
  },
  tools: [
    {
      type: "function",
      function: {
        name: "lookupOrder",
        description: "Look up an order by ID",
        parameters: {
          type: "object",
          properties: { orderId: { type: "string" } },
          required: ["orderId"],
        },
      },
    },
  ],
});

support.on("llm.tool_call", async (data, call) => {
  const results = [];
  for (const tc of data.toolCalls) {
    const args = JSON.parse(tc.arguments);
    const order = await db.orders.findOne(args.orderId);
    results.push({ toolCallId: tc.id, result: order });
  }
  call.toolResult(data.msgId, results);
});

Multi-channel: voice + WhatsApp on the same agent#

The same agent can serve WhatsApp and phone calls. The LLM config, tools, and prompt are shared — only the transport differs.

const support = pc.agent("support", {
  voice: "elevenlabs:abc",
  language: "en",
  llm: { provider: "openai", model: "gpt-4.1-mini", enabled: true, prompt: "..." },
});

support.addChannel("whatsapp", { /* WhatsApp config */ });
support.addChannel("phone", "+13186330963");
support.addChannel("webrtc");

// Voice greeting (WhatsApp doesn't use this)
support.on("call.started", (call) => {
  if (call.transport === "phone" || call.transport === "webrtc") {
    call.say("Hello!");
  }
});

// Shared tool handling for all channels
support.on("llm.tool_call", async (data, call) => {
  // works for both voice and WhatsApp calls
});

Voice notes#

When a user sends a voice note on WhatsApp, the server automatically:

  1. Downloads the audio (OGG/Opus) from the Cloud API
  2. Transcribes it using Deepgram Nova-3
  3. Feeds the transcript to the LLM as if it were a text message

The agent sees voice notes as regular text. No extra code.

Requires DEEPGRAM_API_KEY set on the voice server.

The 24-hour service window#

Meta enforces a 24-hour service window for free-form messaging:

  • Inside the window: the agent can send any text. The window refreshes on every inbound message.
  • Outside the window: only pre-approved template messages can be sent.

The SDK tracks the window automatically. If you try to send when it's closed, the server logs a warning. Template message support is on the roadmap.

Environment variables#

Set these on the voice server (sdk-server):

VariableRequiredDescription
WHATSAPP_VERIFY_TOKENHub verification token (default: pinecall-wa-verify)
WHATSAPP_APP_SECRETMeta App Secret for webhook HMAC verification
DEEPGRAM_API_KEYFor voice notesRequired if you want voice note transcription

All WhatsApp events#

EventData fieldsWhen
whatsapp.session_startedsessionId, contactPhone, contactNameFirst message from a new contact
whatsapp.messagesessionId, from, name, type, text, messageIdIncoming message received
whatsapp.responsesessionId, to, textAgent sent a response
whatsapp.statusstatus, recipient, messageIdDelivery status update

Status values: sentdeliveredread.

What's next#