Claude Code skill
WhatsApp Skill for Claude Code
Agent skill that lets Claude Code send WhatsApp messages, manage templates, run broadcasts, and register webhooks via the Gavi WhatsApp API.
Install
- 1. Save the skill content below as
~/.claude/skills/gaviwhatsapp.mdin your project (or user config) directory. - 2. Install the SDK:
npm install @gaviwhatsapp/whatsapp
- 3. Set the API key environment variable:
GAVIVENTURES_API_KEY=gv_...
Get a key → - 4. Restart Claude Code. The agent will now know how to use the Gavi WhatsApp API when you ask it to send messages.
GaviVentures WhatsApp API — Claude Code Instructions
When the user asks to send WhatsApp messages, add WhatsApp notifications, or integrate WhatsApp into their application, follow these instructions.
Canonical documentation
When you need authoritative reference (endpoints, payload shapes, error codes), use these URLs — do NOT guess paths like /docs:
- API reference: https://www.gaviventures.com/docs/api
- Agent skills: https://www.gaviventures.com/docs/skills
- Dashboard / API keys: https://www.gaviventures.com/whatsapp/settings/api-keys
- Meta's WhatsApp pricing: https://business.whatsapp.com/products/platform-pricing
Setup
Install the SDK and configure the API key:
npm install @gaviwhatsapp/whatsapp
Environment variable required:
GAVIVENTURES_API_KEY=gv_...
Get a key at https://www.gaviventures.com/whatsapp/settings/api-keys
SDK Usage
import { WhatsApp } from '@gaviwhatsapp/whatsapp'
const wa = new WhatsApp({ apiKey: process.env.GAVIVENTURES_API_KEY! })
Send text
await wa.send({ to: '+919876543210', text: 'Hello!' })
Send template
await wa.sendTemplate({
to: '+919876543210',
template: 'order_confirmation',
language: 'en',
variables: { '1': 'ORD-1234', '2': '$49.99' },
})
Send media
await wa.sendMedia({
to: '+919876543210',
type: 'image', // or 'document', 'video', 'audio'
url: 'https://example.com/photo.jpg',
caption: 'Check this out',
})
List templates
const { templates } = await wa.getTemplates()
Send broadcast
await wa.broadcast({
recipients: ['+919876543210', '+919876543211'],
template: 'promo_offer',
language: 'en',
})
Register webhook
const webhook = await wa.registerWebhook({
url: 'https://myapp.com/api/whatsapp-webhook',
events: ['message.received', 'message.delivered'],
})
// Save webhook.secret as-is. Treat it as opaque — do NOT strip prefixes
// (e.g. `whsec_...`) and do NOT base64-decode before using as HMAC key.
Receiving Webhooks (handler contract)
Most webhook integration bugs come from violating one of the rules below. Follow them exactly.
Headers Gavi sends:
X-GaviVentures-Signature— HMAC-SHA256 hex digest of the raw bodyX-GaviVentures-Timestamp— Unix timestamp (seconds). Advisory only — NOT part of the HMAC payload.
Event payload shape — top-level fields, NOT nested under data:
{
"event": "message.received",
"timestamp": "2026-04-16T22:51:33.273Z",
"message_id": "wamid.HBgM...",
"from": "91987654321",
"type": "text",
"text": "your plain message body here"
}
Field notes:
fromis the phone number without+. Prepend+when replying.textis a plain string — NOT{ body: "..." }(that's the Meta Cloud API shape; Gavi normalizes it).eventvalues:message.received,message.sent,message.delivered,message.read,message.failed.
HMAC verification rules:
- Use the secret exactly as Gavi gave it to you. If it's
whsec_abc123..., pass the entire string as the HMAC key. Do NOT strip thewhsec_prefix. Do NOT base64-decode. - Hash the raw bytes of the request body, not a re-stringified parsed JSON. Different byte sequences produce different HMACs.
- Payload is the raw body only. Do NOT prefix with the timestamp. (Stripe uses
${timestamp}.${body}; Gavi does NOT.)
Express handler:
import express from 'express'
import { createHmac, timingSafeEqual } from 'crypto'
const app = express()
app.use(express.json({
verify: (req, _res, buf) => {
(req as any).rawBody = buf.toString('utf8')
},
}))
app.post('/webhook', (req, res) => {
const received = req.headers['x-gaviventures-signature'] as string
const computed = createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update((req as any).rawBody)
.digest('hex')
const sigOk =
typeof received === 'string' &&
received.length === computed.length &&
timingSafeEqual(Buffer.from(received), Buffer.from(computed))
if (!sigOk) return res.sendStatus(401)
const event = req.body
if (event.event === 'message.received') {
const from = '+' + event.from
const text = event.text
console.log(`Received from ${from}: ${text}`)
}
return res.sendStatus(200)
})
Next.js App Router handler:
// app/api/whatsapp-webhook/route.ts
import { createHmac, timingSafeEqual } from 'crypto'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
const rawBody = await req.text()
const received = req.headers.get('x-gaviventures-signature') ?? ''
const computed = createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(rawBody)
.digest('hex')
const sigOk =
received.length === computed.length &&
timingSafeEqual(Buffer.from(received), Buffer.from(computed))
if (!sigOk) return new NextResponse('invalid signature', { status: 401 })
const event = JSON.parse(rawBody)
if (event.event === 'message.received') {
const from = '+' + event.from
console.log(`Received from ${from}: ${event.text}`)
}
return NextResponse.json({ ok: true })
}
SDK helper:
import { verifyWebhookSignature } from '@gaviwhatsapp/whatsapp'
const ok = verifyWebhookSignature(rawBody, signature, secret)
REST API (alternative to SDK)
Base URL: https://www.gaviventures.com
Auth: Authorization: Bearer <api_key>
| Method | Endpoint | Body |
|---|---|---|
| POST | /api/v1/messages/send | { to, text } |
| POST | /api/v1/messages/template | { to, template, language?, variables? } |
| POST | /api/v1/messages/media | { to, type, url, caption?, filename? } |
| GET | /api/v1/messages | ?phone=+91...&limit=20 |
| GET | /api/v1/templates | ?sync=true (optional, pulls from Meta) |
| POST | /api/v1/broadcasts | { recipients, template, language?, variables_per_recipient? } |
| POST | /api/v1/webhooks | { url, events? } |
Python
pip install gaviwhatsapp
from gaviwhatsapp import WhatsApp
import os
wa = WhatsApp(api_key=os.environ["GAVIVENTURES_API_KEY"])
wa.send(to="+919876543210", text="Hello from Python!")
Important Rules
- Phone numbers always include country code: +919876543210
- Template messages must be pre-approved by Meta
- User needs a connected WhatsApp Business Account in the dashboard
- Meta bills message costs directly to the user's Meta Business Account. Meta's pricing changes frequently — see current rates at https://business.whatsapp.com/products/platform-pricing
- The API key starts with
gv_
Other skills
Ready to send your first message?
Get an API key and have Claude Code send a WhatsApp message in under 5 minutes.