agentleFS
sennaBrunosennaBruno/claude-skills/skills/evolution-api-management/SKILL.md

evolution-api-management

Skill1 starsChanged 8 months agoApache-2.0View on GitHub

Use when deploying, connecting, troubleshooting, or managing Evolution API WhatsApp instances on ECS Fargate. Triggers on QR code generation, WhatsApp session conflicts, webhook JWT errors, instance lifecycle operations, or environment isolation for WhatsApp services.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All endpoints require header: apikey: $AUTHENTICATION_API_KEY

    line 26
  2. Very specific:

    Use grep or tail to extract useful data

    line 50
  3. Very specific:

    Call /instance/connect/{name} - returns { code, base64, pairingCode }

    line 99
  4. Distinctive:

    Create HTML with embedded base64 image (no CDN dependency)

    line 102
  5. Very specific:

    When WHATSAPP_MOCK=true, getEvolutionStatus() must return early with a healthy stub.

    line 133

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Evolution API Management

Overview

Operational guide for Evolution API v2.3.7 on ECS Fargate with WhatsApp Baileys integration. Covers instance lifecycle, session management, environment isolation, and troubleshooting.

Core constraint: WhatsApp allows only ONE active Baileys connection per phone number. Two instances with the same number = continuous conflict/replaced loop every ~2 seconds.

Quick Reference

OperationEndpointMethod
List instances/instance/fetchInstancesGET
Create instance/instance/createPOST
Connect (QR)/instance/connect/{name}GET
Connection state/instance/connectionState/{name}GET
Logout/instance/logout/{name}DELETE
Delete instance/instance/delete/{name}DELETE
Set webhook/webhook/set/{name}POST

All endpoints require header: apikey: $AUTHENTICATION_API_KEY

ECS Exec Commands

Container is BusyBox-based: no curl/wget. Use node -e for everything.

# Template for all ECS exec commands
aws ecs execute-command \
  --cluster "$CLUSTER" \
  --task "$TASK_ID" \
  --container EvolutionService \
  --interactive \
  --command 'node -e "...script..."'

Get task ID:

TASK_ID=$(aws ecs list-tasks --cluster "$CLUSTER" --service-name EvolutionService \
  --query 'taskArns[0]' --output text | awk -F/ '{print $NF}')

ECS exec output gotchas:

  • Output always contains Starting session... and Cannot perform start session: EOF noise
  • Use grep or tail to extract useful data
  • Long outputs get truncated (~3KB). For large data: write to file inside container, read in chunks via base64
  • Shell escaping through ECS exec is fragile. For complex payloads: base64-encode the JSON body, decode inside the node script

Instance Lifecycle

Create Instance with Webhook

// Body for POST /instance/create
{
  instanceName: 'alinhou-production',
  integration: 'WHATSAPP-BAILEYS',
  qrcode: true,
  rejectCall: false,
  groupsIgnore: true,
  alwaysOnline: false,
  readMessages: false,
  readStatus: false,
  syncFullHistory: false,
  webhook: {
    url: 'https://lambda-url/api/v1/webhook/evolution',
    byEvents: true,
    base64: false,
    events: ['MESSAGES_UPSERT'],
    headers: { jwt_key: 'YOUR_WEBHOOK_SECRET' }  // MUST have a value
  }
}

Update Webhook on Existing Instance

// POST /webhook/set/{instanceName}
// MUST wrap in "webhook" property
{
  webhook: {
    enabled: true,
    url: 'https://lambda-url/api/v1/webhook/evolution',
    webhookByEvents: true,
    webhookBase64: false,
    events: ['MESSAGES_UPSERT'],
    headers: { jwt_key: 'YOUR_WEBHOOK_SECRET' }
  }
}

QR Code Generation Workflow

  1. Call /instance/connect/{name} - returns { code, base64, pairingCode }
  2. The code field is the raw QR data string (~240 chars)
  3. Generate QR locally: npx qrcode -o /tmp/qr.png "$CODE" or use require('qrcode').toDataURL()
  4. Create HTML with embedded base64 image (no CDN dependency)
  5. Copy to Windows: /mnt/c/Users/{user}/Downloads/
  6. QR expires in ~45 seconds - be fast

Never try to pipe base64 image data through ECS exec - it gets truncated and corrupted.

Environment Isolation

The Problem

WhatsApp Baileys protocol enforces single connection per phone number. Staging + production competing = session conflict loop.

The Solution

ComponentStagingProduction
WHATSAPP_MOCK'true''false'
EVOLUTION_INSTANCE_NAMEalinhou-stagingalinhou-production
ECS scaling{ min: 0, max: 0 }{ min: 1, max: 1 }
Evolution DB/evolution/evolution_prod
Outage checkerReturns mock stubReal Evolution calls

In SST infra:

WHATSAPP_MOCK: $app.stage === 'production' ? 'false' : 'true',
EVOLUTION_INSTANCE_NAME: `alinhou-${$app.stage}`,
scaling: $app.stage === 'production' ? { min: 1, max: 1 } : { min: 0, max: 0 },

Outage Checker Mock Guard

When WHATSAPP_MOCK=true, getEvolutionStatus() must return early with a healthy stub. Otherwise, it tries to reach a non-existent Evolution container and triggers false-positive outage alert emails after 30 minutes.

Webhook JWT Configuration

  • jwt_key goes inside headers object in the webhook config
  • Evolution strips jwt_key, generates HS256 JWT with { app: "evolution", action: "webhook" }
  • JWT has 10-minute expiry, sent as Authorization: Bearer <token>
  • Lambda verifies using EVOLUTION_WEBHOOK_SECRET env var
  • EVOLUTION_WEBHOOK_SECRET is NOT in the Evolution container env - only in Lambda. When creating instances via ECS exec, you must pass the secret value directly (get from npx sst secret list)
  • Missing JWT key causes: secretOrPrivateKey must have a value error in Evolution logs

Session Persistence

Dual strategy for maximum resilience:

  • Database: PostgreSQL Session table (when DATABASE_SAVE_DATA_INSTANCE=true)
  • EFS: Mounted at /evolution/instances/ for local Baileys auth files

Either alone is sufficient. Both together = defense in depth.

Deploy Strategy (Stop-First)

// In infra/evolution.ts transform
deploymentMaximumPercent: 100,
deploymentMinimumHealthyPercent: 0,

Old task stops before new starts. ~3-5 min downtime acceptable:

  • Outbound messages queue in SQS
  • Inbound messages buffered by WhatsApp servers

Brazilian Phone Normalization

WhatsApp sends 12-digit format: 558188101603 (drops mobile 9th digit) Database stores 13-digit format: 5581988101603 (with the 9)

Webhook normalizes: if 12 digits starting with 55, insert 9 at position 4.

Troubleshooting

SymptomCauseFix
conflict/replaced loop every ~2sTwo instances sharing same phoneScale one environment to 0
secretOrPrivateKey must have a valueJWT key empty in webhook configUpdate webhook with POST /webhook/set/{name} and actual secret value
Instance stuck in connectingSession conflict or stale authLogout, wait 5s, reconnect with fresh QR
403 on instance createInstance name already existsDelete old instance first
QR scan says "cannot connect device"Session conflict with another environmentEnsure only one environment has Evolution running
Messages not deliveredPhone number format mismatchCheck 12→13 digit normalization in webhook
Zombie instance (DB=open, WS=dead)Container restart without clean disconnectDelete from DB, clear EFS, force new deployment, recreate instance

Container Details

  • Image: evoapicloud/evolution-api:v2.3.7 (don't upgrade without staging test)
  • BusyBox-based: no curl, no wget, use node -e
  • startPeriod: 180s (container + EFS mount + Prisma + Baileys reconnect)
  • Health check: node HTTP GET to localhost:8080
  • LOG_LEVEL: 'ERROR,WARN,WEBHOOKS' for clean CloudWatch

Cost

Staging Evolution running 24/7 (0.25 vCPU, 1GB ARM64, sa-east-1): ~$8.51/month. Scaling to 0 with mock mode saves this entirely.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.