
- 47 installs
- 44 repo stars
- Updated July 8, 2026
- aviz85/claude-skills-library
whatsapp is a Claude skill that automates WhatsApp via the Green API to send messages, voice notes, and images and read group members.
About
whatsapp automates WhatsApp through the Green API using bundled TypeScript scripts to send text messages, voice notes, and images, and to extract group members. A developer uses it to send messages or confirm delivery from an agent, with phone-format normalization and a dry-run preview. It pairs with the get-contact and speech-generator skills for lookups and TTS voice notes.
- Send WhatsApp text, voice notes, and images via the Green API
- Get group members and normalize multiple phone formats
- --dry-run preview before bulk operations
Whatsapp by the numbers
- 47 all-time installs (skills.sh)
- Ranked #1,109 of 2,719 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
whatsapp capabilities & compatibility
Requires Green API credentials via SETUP.md; ships with setup_complete false.
- Capabilities
- speech generator · zoom meeting
- Use cases
- Pricing
- Bring your own API key
What whatsapp says it does
WhatsApp automation using Green API. Send messages, voice notes, images, and get group members.
Use `--dry-run` to preview before bulk operations
Voice notes require `ffmpeg` installed
npx skills add https://github.com/aviz85/claude-skills-library --skill whatsappAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 47 |
|---|---|
| repo stars | ★ 44 |
| Last updated | July 8, 2026 |
| Repository | aviz85/claude-skills-library ↗ |
What it does
Send WhatsApp messages, voice notes, or images and read group members from an agent via the Green API.
Who is it for?
Sending WhatsApp text, voice, or image messages and extracting group phone numbers from an agent.
Skip if: Messaging platforms other than WhatsApp; it is specific to the Green API.
When should I use this skill?
User wants to send a WhatsApp message, voice note, or image, or get group members.
What you get
A delivered WhatsApp message, voice note, or image, confirmed via the API response.
- Sent WhatsApp messages
- Group member phone numbers
By the numbers
- Four bundled scripts (message, voice, image, group members)
Files
WhatsApp Automation (Green API)
First time? Ifsetup_complete: falseabove, run./SETUP.mdfirst, then setsetup_complete: true.
Send messages and get group information via WhatsApp.
Workflow
1. Get contact - Use get-contact skill or ask user for phone 2. Send message - Text, voice, image, or file 3. Confirm delivery - Check response for success
Scripts
All scripts in scripts/ folder:
| Script | Use |
|---|---|
send-message.ts | Text messages |
send-voice.ts | Voice notes (converts to OGG) |
send-image.ts | Images with captions |
get-group-members.ts | Extract group phone numbers |
Quick Examples
cd scripts/
# Text message
npx ts-node send-message.ts --phone "972501234567" --message "Hello!"
# Voice note
npx ts-node send-voice.ts --phone "972501234567" --audio "/path/audio.mp3"
# Image with caption
npx ts-node send-image.ts --phone "972501234567" --image "/path/image.jpg" --caption "Check this!"
# Preview without sending
npx ts-node send-message.ts --phone "972501234567" --message "Test" --dry-runPhone Formats
| Input | Normalized |
|---|---|
0501234567 | 972501234567@c.us |
+972501234567 | 972501234567@c.us |
972501234567 | 972501234567@c.us |
Default Numbers
Configure your test number in skill for quick access:
| Alias | Number |
|---|---|
| myself / me / test | YOUR_PHONE_NUMBER |
Notes
- Use
--dry-runto preview before bulk operations - Voice notes require
ffmpeginstalled - Rate limits apply when sending many messages
import * as dotenv from "dotenv";
import * as path from "path";
// Load environment variables
dotenv.config({ path: path.join(__dirname, ".env") });
const API_URL = process.env.GREEN_API_URL || "https://api.green-api.com";
const INSTANCE_ID = process.env.GREEN_API_INSTANCE;
const API_TOKEN = process.env.GREEN_API_TOKEN;
interface Participant {
id: string;
isAdmin: boolean;
isSuperAdmin: boolean;
}
interface GroupData {
groupId: string;
owner: string;
subject: string;
creation: number;
participants: Participant[];
size: number;
groupInviteLink?: string;
}
interface Args {
groupId: string;
phonesOnly: boolean;
json: boolean;
}
function parseArgs(): Args {
const args = process.argv.slice(2);
const result: Args = {
groupId: "",
phonesOnly: false,
json: false,
};
for (let i = 0; i < args.length; i++) {
if (args[i] === "--phones-only") {
result.phonesOnly = true;
} else if (args[i] === "--json") {
result.json = true;
} else if (!args[i].startsWith("--")) {
result.groupId = args[i];
}
}
return result;
}
function normalizePhone(whatsappId: string): string {
// Remove @c.us or @s.whatsapp.net suffix
return whatsappId.replace(/@.*$/, "");
}
async function getGroupData(groupId: string): Promise<GroupData> {
const url = `${API_URL}/waInstance${INSTANCE_ID}/getGroupData/${API_TOKEN}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ groupId }),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`API request failed: ${response.status} - ${text}`);
}
return response.json();
}
async function main() {
// Validate credentials
if (!INSTANCE_ID || !API_TOKEN) {
console.error("Error: Missing credentials!");
console.error("Please configure GREEN_API_INSTANCE and GREEN_API_TOKEN in .env file");
console.error("");
console.error("Create .env file with:");
console.error(" GREEN_API_URL=https://7103.api.greenapi.com");
console.error(" GREEN_API_INSTANCE=your_instance_id");
console.error(" GREEN_API_TOKEN=your_api_token");
process.exit(1);
}
const args = parseArgs();
if (!args.groupId) {
console.error("Usage: npx ts-node get-group-members.ts <GROUP_ID> [options]");
console.error("");
console.error("Options:");
console.error(" --phones-only Output only phone numbers (one per line)");
console.error(" --json Output full JSON data");
console.error("");
console.error("Examples:");
console.error(" npx ts-node get-group-members.ts 120363044291817037@g.us");
console.error(" npx ts-node get-group-members.ts 120363044291817037@g.us --phones-only");
console.error(" npx ts-node get-group-members.ts 120363044291817037@g.us --phones-only > phones.txt");
process.exit(1);
}
// Ensure group ID has correct format
const groupId = args.groupId.includes("@g.us") ? args.groupId : `${args.groupId}@g.us`;
try {
const groupData = await getGroupData(groupId);
// Extract phone numbers
const phones = groupData.participants.map((p) => normalizePhone(p.id));
if (args.phonesOnly) {
// Output just phone numbers, one per line
phones.forEach((phone) => console.log(phone));
} else if (args.json) {
// Full JSON output
console.log(JSON.stringify({
groupId: groupData.groupId,
subject: groupData.subject,
size: groupData.size,
owner: normalizePhone(groupData.owner),
participants: groupData.participants.map((p) => ({
phone: normalizePhone(p.id),
isAdmin: p.isAdmin,
isSuperAdmin: p.isSuperAdmin,
})),
}, null, 2));
} else {
// Human-readable output
console.log(`\n=== ${groupData.subject} ===\n`);
console.log(`Group ID: ${groupData.groupId}`);
console.log(`Size: ${groupData.size} members`);
console.log(`Owner: ${normalizePhone(groupData.owner)}`);
console.log("");
const admins = groupData.participants.filter((p) => p.isAdmin || p.isSuperAdmin);
const members = groupData.participants.filter((p) => !p.isAdmin && !p.isSuperAdmin);
console.log(`Admins (${admins.length}):`);
admins.forEach((p) => {
const role = p.isSuperAdmin ? "Owner" : "Admin";
console.log(` ${normalizePhone(p.id)} [${role}]`);
});
console.log(`\nMembers (${members.length}):`);
members.forEach((p) => {
console.log(` ${normalizePhone(p.id)}`);
});
console.log(`\n=== All Phone Numbers ===\n`);
phones.forEach((phone) => console.log(phone));
}
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
{
"name": "whatsapp-skill",
"version": "1.0.0",
"description": "WhatsApp automation using Green API",
"scripts": {
"get-group": "ts-node get-group-members.ts",
"send": "ts-node send-message.ts"
},
"dependencies": {
"dotenv": "^16.3.1",
"form-data": "^4.0.5"
},
"devDependencies": {
"@types/node": "^20.10.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.2"
}
}
import * as dotenv from "dotenv";
import * as path from "path";
import * as fs from "fs";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
// Load environment variables
dotenv.config({ path: path.join(__dirname, ".env") });
const API_URL = process.env.GREEN_API_URL || "https://api.green-api.com";
const INSTANCE_ID = process.env.GREEN_API_INSTANCE;
const API_TOKEN = process.env.GREEN_API_TOKEN;
interface Args {
phone?: string;
imagePath?: string;
caption?: string;
dryRun: boolean;
}
function parseArgs(): Args {
const args = process.argv.slice(2);
const result: Args = {
dryRun: false,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case "--phone":
result.phone = args[++i];
break;
case "--image":
result.imagePath = args[++i];
break;
case "--caption":
result.caption = args[++i];
break;
case "--dry-run":
result.dryRun = true;
break;
}
}
return result;
}
function normalizePhone(phone: string): string {
let digits = phone.replace(/\D/g, "");
if (digits.startsWith("972")) {
// Already correct
} else if (digits.startsWith("0")) {
digits = "972" + digits.substring(1);
} else if (digits.length === 9) {
digits = "972" + digits;
}
return digits;
}
function formatChatId(id: string): string {
const cleanNumber = normalizePhone(id);
return `${cleanNumber}@c.us`;
}
async function sendFileByUpload(
chatId: string,
filePath: string,
caption?: string
): Promise<{ idMessage: string; urlFile: string }> {
const url = `${API_URL}/waInstance${INSTANCE_ID}/sendFileByUpload/${API_TOKEN}`;
const fileName = path.basename(filePath);
// Build curl command - more reliable for multipart form data
let curlCmd = `curl -s --location "${url}" -F 'chatId=${chatId}' -F 'file=@${filePath}' -F 'fileName=${fileName}'`;
if (caption) {
// Escape single quotes in caption
const escapedCaption = caption.replace(/'/g, "'\\''");
curlCmd += ` -F 'caption=${escapedCaption}'`;
}
const { stdout, stderr } = await execAsync(curlCmd);
if (stderr && !stdout) {
throw new Error(`Curl error: ${stderr}`);
}
try {
return JSON.parse(stdout);
} catch (e) {
throw new Error(`Invalid response: ${stdout}`);
}
}
async function main() {
if (!INSTANCE_ID || !API_TOKEN) {
console.error("Error: Missing credentials!");
console.error("Please configure GREEN_API_INSTANCE and GREEN_API_TOKEN in .env file");
process.exit(1);
}
const args = parseArgs();
if (!args.phone || !args.imagePath) {
console.error("Usage:");
console.error(' npx ts-node send-image.ts --phone "972501234567" --image "/path/to/image.jpg"');
console.error(' npx ts-node send-image.ts --phone "972501234567" --image "/path/to/image.jpg" --caption "Check this out!"');
console.error("");
console.error("Options:");
console.error(" --phone <NUMBER> Phone number (international format, no +)");
console.error(" --image <PATH> Path to image file");
console.error(" --caption <TEXT> Optional caption for the image");
console.error(" --dry-run Preview without sending");
process.exit(1);
}
// Check if file exists
if (!fs.existsSync(args.imagePath)) {
console.error(`Error: File not found: ${args.imagePath}`);
process.exit(1);
}
try {
const chatId = formatChatId(args.phone);
const fileName = path.basename(args.imagePath);
const fileSize = fs.statSync(args.imagePath).size;
console.log(`Sending image to: ${chatId}`);
console.log(`File: ${fileName} (${(fileSize / 1024).toFixed(1)} KB)`);
if (args.caption) {
console.log(`Caption: ${args.caption.substring(0, 50)}...`);
}
console.log("");
if (args.dryRun) {
console.log("✓ [DRY RUN] Would send image");
return;
}
const result = await sendFileByUpload(chatId, args.imagePath, args.caption);
console.log(`✓ Image sent! ID: ${result.idMessage}`);
console.log(` URL: ${result.urlFile}`);
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
import * as dotenv from "dotenv";
import * as path from "path";
// Load environment variables
dotenv.config({ path: path.join(__dirname, ".env") });
const API_URL = process.env.GREEN_API_URL || "https://api.green-api.com";
const INSTANCE_ID = process.env.GREEN_API_INSTANCE;
const API_TOKEN = process.env.GREEN_API_TOKEN;
interface SendMessageResponse {
idMessage: string;
}
interface Participant {
id: string;
isAdmin: boolean;
isSuperAdmin: boolean;
}
interface GroupData {
groupId: string;
participants: Participant[];
subject: string;
}
interface Args {
groupId?: string;
phone?: string;
message?: string;
dmAll: boolean;
dryRun: boolean;
}
function parseArgs(): Args {
const args = process.argv.slice(2);
const result: Args = {
dmAll: false,
dryRun: false,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case "--group":
result.groupId = args[++i];
break;
case "--phone":
result.phone = args[++i];
break;
case "--message":
result.message = args[++i];
break;
case "--dm-all":
result.dmAll = true;
break;
case "--dry-run":
result.dryRun = true;
break;
}
}
return result;
}
function normalizePhone(phone: string): string {
// Remove all non-digits
let digits = phone.replace(/\D/g, "");
// Handle Israeli numbers
if (digits.startsWith("972")) {
// Already correct
} else if (digits.startsWith("0")) {
digits = "972" + digits.substring(1);
} else if (digits.length === 9) {
digits = "972" + digits;
}
return digits;
}
function formatChatId(id: string, isGroup: boolean): string {
if (isGroup) {
return id.includes("@g.us") ? id : `${id}@g.us`;
}
const cleanNumber = normalizePhone(id);
return `${cleanNumber}@c.us`;
}
async function sendMessage(chatId: string, message: string): Promise<SendMessageResponse> {
const url = `${API_URL}/waInstance${INSTANCE_ID}/sendMessage/${API_TOKEN}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ chatId, message }),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`API request failed: ${response.status} - ${text}`);
}
return response.json();
}
async function getGroupData(groupId: string): Promise<GroupData> {
const url = `${API_URL}/waInstance${INSTANCE_ID}/getGroupData/${API_TOKEN}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ groupId }),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`API request failed: ${response.status} - ${text}`);
}
return response.json();
}
async function main() {
// Validate credentials
if (!INSTANCE_ID || !API_TOKEN) {
console.error("Error: Missing credentials!");
console.error("Please configure GREEN_API_INSTANCE and GREEN_API_TOKEN in .env file");
console.error("");
console.error("Create .env file with:");
console.error(" GREEN_API_URL=https://7103.api.greenapi.com");
console.error(" GREEN_API_INSTANCE=your_instance_id");
console.error(" GREEN_API_TOKEN=your_api_token");
process.exit(1);
}
const args = parseArgs();
// Validate arguments
if (!args.message) {
console.error("Usage:");
console.error(' npx ts-node send-message.ts --phone "972501234567" --message "Hello!"');
console.error(' npx ts-node send-message.ts --group "GROUP_ID" --message "Hello group!"');
console.error(' npx ts-node send-message.ts --group "GROUP_ID" --dm-all --message "Personal msg"');
console.error("");
console.error("Options:");
console.error(" --phone <NUMBER> Phone number (international format, no +)");
console.error(" --group <ID> Group ID (format: 120363xxx@g.us)");
console.error(" --message <TEXT> Message text to send");
console.error(" --dm-all Send DM to each group participant");
console.error(" --dry-run Preview without sending");
process.exit(1);
}
if (!args.groupId && !args.phone) {
console.error("Error: Either --group or --phone is required");
process.exit(1);
}
try {
if (args.dryRun) {
console.log("=== DRY RUN MODE - No messages will be sent ===\n");
}
// Send to individual phone
if (args.phone && !args.groupId) {
const chatId = formatChatId(args.phone, false);
console.log(`Sending to: ${chatId}`);
console.log(`Message: ${args.message}\n`);
if (!args.dryRun) {
const result = await sendMessage(chatId, args.message);
console.log(`✓ Message sent! ID: ${result.idMessage}`);
} else {
console.log("✓ [DRY RUN] Would send message");
}
return;
}
// Send to group
if (args.groupId && !args.dmAll) {
const chatId = formatChatId(args.groupId, true);
console.log(`Sending to group: ${chatId}`);
console.log(`Message: ${args.message}\n`);
if (!args.dryRun) {
const result = await sendMessage(chatId, args.message);
console.log(`✓ Message sent to group! ID: ${result.idMessage}`);
} else {
console.log("✓ [DRY RUN] Would send message to group");
}
return;
}
// DM all participants
if (args.groupId && args.dmAll) {
const groupId = formatChatId(args.groupId, true);
console.log(`Fetching participants from group: ${groupId}\n`);
const groupData = await getGroupData(groupId);
console.log(`Group: ${groupData.subject}`);
console.log(`Found ${groupData.participants.length} participants\n`);
console.log(`Message to send: ${args.message}\n`);
let successCount = 0;
let failCount = 0;
for (const participant of groupData.participants) {
// Convert group participant ID to chat ID
const chatId = participant.id.replace("@g.us", "").replace("@s.whatsapp.net", "") + "@c.us";
if (args.dryRun) {
console.log(`[DRY RUN] Would send to: ${chatId}`);
successCount++;
} else {
try {
const result = await sendMessage(chatId, args.message);
console.log(`✓ Sent to ${chatId} (ID: ${result.idMessage})`);
successCount++;
// Small delay to avoid rate limiting
await new Promise((resolve) => setTimeout(resolve, 500));
} catch (error) {
console.error(`✗ Failed to send to ${chatId}:`, error);
failCount++;
}
}
}
console.log(`\n=== Summary ===`);
console.log(`Successful: ${successCount}`);
console.log(`Failed: ${failCount}`);
}
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
import * as dotenv from "dotenv";
import * as path from "path";
import * as fs from "fs";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
// Load environment variables
dotenv.config({ path: path.join(__dirname, ".env") });
const API_URL = process.env.GREEN_API_URL || "https://api.green-api.com";
const INSTANCE_ID = process.env.GREEN_API_INSTANCE;
const API_TOKEN = process.env.GREEN_API_TOKEN;
interface Args {
phone?: string;
group?: string;
audioPath?: string;
dryRun: boolean;
}
function parseArgs(): Args {
const args = process.argv.slice(2);
const result: Args = {
dryRun: false,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case "--phone":
result.phone = args[++i];
break;
case "--group":
result.group = args[++i];
break;
case "--audio":
result.audioPath = args[++i];
break;
case "--dry-run":
result.dryRun = true;
break;
}
}
return result;
}
function normalizePhone(phone: string): string {
let digits = phone.replace(/\D/g, "");
if (digits.startsWith("972")) {
// Already correct
} else if (digits.startsWith("0")) {
digits = "972" + digits.substring(1);
} else if (digits.length === 9) {
digits = "972" + digits;
}
return digits;
}
function formatChatId(id: string): string {
const cleanNumber = normalizePhone(id);
return `${cleanNumber}@c.us`;
}
async function convertToOgg(inputPath: string): Promise<string> {
const outputPath = inputPath.replace(/\.[^.]+$/, ".ogg");
// Convert to OGG with opus codec for WhatsApp voice notes
// Settings: mono, 48kHz sample rate, 32k bitrate (WhatsApp requirements)
const cmd = `ffmpeg -y -i "${inputPath}" -ac 1 -ar 48000 -b:a 32k -c:a libopus "${outputPath}"`;
console.log("Converting to OGG (opus) for WhatsApp voice note...");
try {
await execAsync(cmd);
return outputPath;
} catch (error: any) {
throw new Error(`FFmpeg conversion failed: ${error.message}`);
}
}
async function sendFileByUpload(
chatId: string,
filePath: string
): Promise<{ idMessage: string; urlFile: string }> {
const url = `${API_URL}/waInstance${INSTANCE_ID}/sendFileByUpload/${API_TOKEN}`;
const fileName = path.basename(filePath);
// Build curl command for multipart form data
const curlCmd = `curl -s --location "${url}" -F 'chatId=${chatId}' -F 'file=@${filePath}' -F 'fileName=${fileName}'`;
const { stdout, stderr } = await execAsync(curlCmd);
if (stderr && !stdout) {
throw new Error(`Curl error: ${stderr}`);
}
try {
return JSON.parse(stdout);
} catch (e) {
throw new Error(`Invalid response: ${stdout}`);
}
}
async function main() {
if (!INSTANCE_ID || !API_TOKEN) {
console.error("Error: Missing credentials!");
console.error("Please configure GREEN_API_INSTANCE and GREEN_API_TOKEN in .env file");
process.exit(1);
}
const args = parseArgs();
if ((!args.phone && !args.group) || !args.audioPath) {
console.error("Usage:");
console.error(' npx ts-node send-voice.ts --phone "972501234567" --audio "/path/to/audio.mp3"');
console.error(' npx ts-node send-voice.ts --group "120363xxx@g.us" --audio "/path/to/audio.mp3"');
console.error("");
console.error("Options:");
console.error(" --phone <NUMBER> Phone number (international format, no +)");
console.error(" --group <ID> Group ID (format: 120363xxx@g.us)");
console.error(" --audio <PATH> Path to audio file (will be converted to OGG)");
console.error(" --dry-run Preview without sending");
process.exit(1);
}
// Check if file exists
if (!fs.existsSync(args.audioPath)) {
console.error(`Error: File not found: ${args.audioPath}`);
process.exit(1);
}
try {
// Use group ID directly if provided, otherwise format phone
const chatId = args.group ? args.group : formatChatId(args.phone!);
const fileSize = fs.statSync(args.audioPath).size;
console.log(`\n=== Send Voice Message ===`);
console.log(`To: ${chatId}`);
console.log(`File: ${path.basename(args.audioPath)} (${(fileSize / 1024).toFixed(1)} KB)`);
console.log("");
if (args.dryRun) {
console.log("✓ [DRY RUN] Would convert and send voice message");
return;
}
// Convert to OGG if not already
let oggPath = args.audioPath;
if (!args.audioPath.toLowerCase().endsWith(".ogg")) {
oggPath = await convertToOgg(args.audioPath);
console.log(`Converted: ${path.basename(oggPath)}`);
}
// Send as voice note
const result = await sendFileByUpload(chatId, oggPath);
console.log(`✓ Voice message sent! ID: ${result.idMessage}`);
// Clean up converted file if we created it
if (oggPath !== args.audioPath && fs.existsSync(oggPath)) {
fs.unlinkSync(oggPath);
}
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "."
},
"include": ["*.ts"],
"exclude": ["node_modules"]
}
WhatsApp Skill - Setup Guide
Prerequisites
- Green API account
- WhatsApp phone connected
1. Create Green API Account
1. Go to green-api.com 2. Sign up for free account 3. Create a new instance
2. Authorize Instance
1. Open the instance in console 2. Scan QR code with WhatsApp 3. Wait for "authorized" status
3. Get Credentials
From console.green-api.com:
| Field | Where |
|---|---|
| Instance ID | Instance settings |
| API Token | Instance settings |
| API URL | Usually https://7103.api.greenapi.com |
4. Configure
Create .env in scripts/ folder:
GREEN_API_URL=https://7103.api.greenapi.com
GREEN_API_INSTANCE=your_instance_id
GREEN_API_TOKEN=your_api_token5. Install Dependencies
cd scripts/
npm install6. Test
# Send test message to yourself
npx ts-node send-message.ts --phone "YOUR_PHONE" --message "Test from Claude!" --dry-run
# If dry-run looks good, remove --dry-run flag
npx ts-node send-message.ts --phone "YOUR_PHONE" --message "Test from Claude!"Voice Notes (Optional)
For voice messages, install ffmpeg:
# macOS
brew install ffmpeg
# Ubuntu
sudo apt install ffmpegTroubleshooting
| Issue | Solution |
|---|---|
| 401 error | Check API token |
| Phone not authorized | Rescan QR code |
| Voice fails | Install ffmpeg |
| Rate limited | Wait a few minutes |
Finding Group IDs
Group IDs look like: 120363123456789012@g.us
Ways to find: 1. WhatsApp Web URL contains group ID 2. Use Green API console "Groups" section 3. Ask Claude to list your groups via API
Related skills
FAQ
Which API does the whatsapp skill use?
It uses the Green API to send messages and get group information.
How can you preview before sending in bulk?
Pass --dry-run to preview a message without sending it.