Cursor skill
WhatsApp Skill for Cursor
Drop-in Cursor rule that teaches the AI to send WhatsApp messages, broadcasts, and webhooks via the Gavi WhatsApp API.
Install
- 1. Save the skill content below as
.cursor/rules/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 Cursor. The agent will now know how to use the Gavi WhatsApp API when you ask it to send messages.
GaviVentures WhatsApp API — Cursor Skill
Use this skill whenever the user asks to send WhatsApp messages, add WhatsApp notifications, or integrate WhatsApp into their application.
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 (this page and others): https://www.gaviventures.com/docs/skills
- Dashboard / API key management: https://www.gaviventures.com/whatsapp/settings/api-keys
- Meta's WhatsApp pricing (Gavi does not bill messages — Meta does): https://business.whatsapp.com/products/platform-pricing
Setup
- Install the SDK:
npm install @gaviwhatsapp/whatsapp
- Set the environment variable:
GAVIVENTURES_API_KEY=gv_...
Get an API key at https://www.gaviventures.com/whatsapp/settings/api-keys
Send a Text Message
import { WhatsApp } from '@gaviwhatsapp/whatsapp'
const wa = new WhatsApp({ apiKey: process.env.GAVIVENTURES_API_KEY! })
await wa.send({ to: '+919876543210', text: 'Hello from my app!' })
Send a Template Message
Templates are pre-approved message formats required for initiating conversations.
await wa.sendTemplate({
to: '+919876543210',
template: 'order_confirmation',
language: 'en',
variables: { '1': 'ORD-1234', '2': '$49.99' },
})
Send Media (Image, Document, Video)
await wa.sendMedia({
to: '+919876543210',
type: 'image',
url: 'https://example.com/receipt.png',
caption: 'Your receipt',
})
await wa.sendMedia({
to: '+919876543210',
type: 'document',
url: 'https://example.com/invoice.pdf',
filename: 'invoice.pdf',
})
List Templates
const templates = await wa.getTemplates()
// Returns: { templates: [{ name, status, category, language, ... }] }
Send Broadcast
await wa.broadcast({
recipients: ['+919876543210', '+919876543211'],
template: 'promo_offer',
language: 'en',
variables_per_recipient: [
{ '1': 'Alice', '2': '20%' },
{ '1': 'Bob', '2': '15%' },
],
})
Register a Webhook
Receive real-time delivery updates and incoming messages.
const webhook = await wa.registerWebhook({
url: 'https://myapp.com/api/whatsapp-webhook',
events: ['message.received', 'message.delivered', 'message.read'],
})
// Save webhook.secret — use it to verify HMAC signatures on every event.
// Treat it as opaque; do NOT strip prefixes or decode it.
Receiving Webhooks (handler contract — read carefully)
Most webhook integration bugs come from violating one of the rules below. Follow them exactly.
Headers Gavi sends with every event
X-GaviVentures-Signature— HMAC-SHA256 hex digest of the raw bodyX-GaviVentures-Timestamp— Unix timestamp (seconds). Advisory only — it is NOT part of the HMAC payload.
Event payload shape (top-level fields, NOT nested under a data key)
{
"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 the+(e.g.91987654321). Prepend+when you reply:+91987654321.textis a plain string at the top level — NOT{ body: "..." }. (That nested shape is the Meta Cloud API format; Gavi normalizes it for you.)eventvalues:message.received,message.sent,message.delivered,message.read,message.failed.- For non-
message.receivedevents, fields differ:to,status,message_idare present;fromandtextmay be absent.
HMAC verification rules
- Use the secret exactly as Gavi gave it to you. If your secret looks like
whsec_abc123..., pass the whole string as the HMAC key. Do not strip thewhsec_prefix. Do not base64-decode. Do not hex-decode. Pass it as a UTF-8 string, byte-for-byte as Gavi returned it. - Hash the raw bytes of the request body, NOT a re-stringified version of the parsed JSON. In Express, capture the raw body via the
verifycallback inexpress.json(). In Next.js, callreq.text()before any parsing. - The HMAC payload is the raw body only. Do not prefix it with the timestamp. (Stripe uses
${timestamp}.${body}; Gavi does NOT.)
Express handler (works out of the box)
import express from 'express'
import { createHmac, timingSafeEqual } from 'crypto'
const app = express()
app.use(
express.json({
verify: (req, _res, buf) => {
// Capture the raw body BEFORE JSON parsing — required for HMAC.
;(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 // prepend + for replies
const text = event.text // plain string
console.log(`Received from ${from}: ${text}`)
}
return res.sendStatus(200)
})
app.listen(3001)
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() // raw bytes BEFORE parsing
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
const text = event.text
console.log(`Received from ${from}: ${text}`)
}
return NextResponse.json({ ok: true })
}
SDK helper
import { verifyWebhookSignature } from '@gaviwhatsapp/whatsapp'
const ok = verifyWebhookSignature(rawBody, signature, secret)
REST API (direct)
If not using the SDK, call the API directly:
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/messages/send | Send text message |
| POST | /api/v1/messages/template | Send template message |
| POST | /api/v1/messages/media | Send media message |
| GET | /api/v1/messages?phone=+91... | Get message history |
| GET | /api/v1/templates | List templates |
| POST | /api/v1/broadcasts | Send broadcast |
| GET | /api/v1/webhooks | List webhooks |
| POST | /api/v1/webhooks | Register webhook |
All requests require Authorization: Bearer <api_key> header.
Base URL: https://www.gaviventures.com
Common Patterns
Next.js API Route (order notification)
// app/api/orders/route.ts
import { WhatsApp } from '@gaviwhatsapp/whatsapp'
const wa = new WhatsApp({ apiKey: process.env.GAVIVENTURES_API_KEY! })
export async function POST(req: Request) {
const order = await req.json()
await wa.sendTemplate({
to: order.customerPhone,
template: 'order_confirmation',
language: 'en',
variables: { '1': order.id, '2': order.total },
})
return Response.json({ success: true })
}
Note: a complete webhook handler with HMAC verification is in the Receiving Webhooks section above. Always include signature verification in production — never trust unverified webhook bodies.
Important Notes
- Phone numbers must include country code (e.g., +919876543210)
- Template messages must be approved by Meta before sending
- You need a connected WhatsApp Business Account (set up in the dashboard)
- Message costs are billed directly by Meta to the user's Business Account. Meta's pricing changes frequently — see the current rates at https://business.whatsapp.com/products/platform-pricing
Other skills
Claude Code
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.
Codex
WhatsApp Skill for Codex
AGENTS.md-compatible skill that lets OpenAI Codex add WhatsApp messaging to any project via the Gavi WhatsApp API.
Ready to send your first message?
Get an API key and have Cursor send a WhatsApp message in under 5 minutes.