Skip to main content

Webhooks

Ozzie exposes a webhook endpoint that receives inbound WhatsApp messages routed through Evolution API. When a user sends a message to your WhatsApp instance, Evolution API forwards it to this endpoint, Ozzie processes it, and automatically replies to the user in their language.

This is the foundation for building a WhatsApp-first financial coach without writing any message routing logic yourself.


How it works​

User (WhatsApp) β†’ Evolution API β†’ POST /api/webhooks/evolution β†’ Ozzie processes + replies
↓
User receives reply on WhatsApp
  1. A user sends a WhatsApp message to your Evolution API instance
  2. Evolution API forwards the message payload to your configured webhook URL
  3. Ozzie verifies the request signature, identifies the user by their phone number, and processes the message
  4. Ozzie sends an automatic reply back through Evolution API in the user's configured language

POST /api/webhooks/evolution​

This endpoint is called by Evolution API, not by your server. Your job is to configure Evolution API to point at it. Ozzie handles everything else.

info

Note the path: /api/webhooks/evolution β€” not /v1/.... This endpoint is outside the versioned REST namespace because it conforms to Evolution API's webhook contract, not Ozzie's standard request format.


Setup guide​

Step 1: Configure Evolution API​

In your Evolution API dashboard or configuration, set the webhook URL for your instance to:

https://your-domain.com/api/webhooks/evolution

Replace your-domain.com with the domain where your Ozzie-integrated backend is hosted.

warning

Evolution API will only forward webhook events to HTTPS endpoints. Local development requires a tunnel (e.g., ngrok, Cloudflare Tunnel, or similar).

Step 2: Set the webhook secret​

In your environment configuration, set:

EVOLUTION_WEBHOOK_SECRET=your_shared_secret_here

This must be the same value you configure in Evolution API as the apikey or webhook secret for your instance. Ozzie uses this to verify that incoming requests are genuinely from Evolution API and not spoofed.

Step 3: Register your users with their phone numbers​

For Ozzie to route an inbound WhatsApp message to the right user, the sender's phone number must match a user's phone_e164 field in Ozzie.

When creating users who will interact via WhatsApp, include their phone number in E.164 format:

curl -X POST https://api.ozzieapp.com/v1/users \
-H "Authorization: Bearer ozp_Y2xpZW50X2ExYjJjM2Q0OnNrX2xpdmVfeEs5bVAycVI3dEwu" \
-H "Content-Type: application/json" \
-d '{
"external_user_id": "user_789",
"name": "Sofia Mendes",
"language": "pt",
"phone_e164": "+5511987654321"
}'

If a message arrives from a phone number that doesn't match any registered user, Ozzie will return a 404 to Evolution API and no reply will be sent.


Supported message types​

Evolution API messageTypeWhat the user sendsHow Ozzie processes it
conversationA plain text messageParsed as a chat message or transaction log
imageMessageA photo (e.g., a receipt)Processed by Ozzie's image parser to extract transaction data
info

PDF and spreadsheet parsing is available via the REST transactions endpoint but is not currently supported through the WhatsApp webhook. For document uploads, direct your users to a web interface that calls POST /transactions directly.


Webhook payload format​

This is the payload Evolution API sends to your endpoint. Ozzie parses this format automatically β€” you do not need to transform it.

{
"event": "messages.upsert",
"instance": "your_instance_name",
"data": {
"key": {
"remoteJid": "5511987654321@s.whatsapp.net",
"fromMe": false,
"id": "3EB0A1B2C3D4E5F6A7B8"
},
"messageType": "conversation",
"message": {
"conversation": "spent $45 on groceries at Whole Foods"
}
}
}

Image message payload​

{
"event": "messages.upsert",
"instance": "your_instance_name",
"data": {
"key": {
"remoteJid": "5511987654321@s.whatsapp.net",
"fromMe": false,
"id": "3EB0A1B2C3D4E5F6FFFF"
},
"messageType": "imageMessage",
"message": {
"imageMessage": {
"caption": "my grocery receipt",
"mimetype": "image/jpeg",
"url": "https://media.evolution.example.com/media/abc123.jpg"
}
}
}
}

Key fields​

FieldDescription
data.key.remoteJidThe sender's WhatsApp ID. Ozzie strips the @s.whatsapp.net suffix to get the phone number.
data.key.fromMeIf true, this message was sent by your instance (outbound). Ozzie ignores fromMe: true messages.
data.messageTypeDetermines how Ozzie processes the message body.
data.message.conversationThe text content (for conversation type).

Security​

Ozzie verifies every inbound webhook request by checking the apikey header against the value stored in your EVOLUTION_WEBHOOK_SECRET environment variable.

POST /api/webhooks/evolution
apikey: your_shared_secret_here
Content-Type: application/json

If the header is missing or the value does not match, Ozzie returns 401 Unauthorized and discards the message.

Security best practices​

Use a strong, random secret. Generate at least 32 bytes of random data:

openssl rand -hex 32
# e.g.: a7f3c8e1b2d4e5f60718293a4b5c6d7e8f901234567890abcdef01234567890

Rotate secrets periodically. Update EVOLUTION_WEBHOOK_SECRET and your Evolution API configuration at the same time during a low-traffic window to avoid dropped messages during the transition.

Use HTTPS only. Never expose the webhook endpoint over plain HTTP in production. All webhook traffic should be encrypted in transit.

Restrict inbound traffic by IP (optional). If Evolution API has a fixed egress IP range for your instance, configure your firewall or reverse proxy to only allow requests from those IPs.


Ozzie's automatic replies​

When Ozzie successfully processes an inbound message, it sends a reply back through Evolution API automatically. The reply is generated by the same AI coach as POST /chat/messages and is returned in the user's configured language.

Example: text message in β†’ coach reply out

User sends (WhatsApp):

I just paid my electricity bill β€” $120

Ozzie replies (WhatsApp):

Got it! I've logged $120 for Electricity under Utilities πŸ’‘ Your utilities total for May is now $195, which is within your budget. You're doing great this month β€” keep it up!

Example: receipt photo in β†’ parsed transaction out

User sends a photo of a grocery receipt.

Ozzie replies:

I parsed your receipt from Trader Joe's: $87.40 logged under Groceries for May 5th. Your grocery total for May is now $302. You have about $98 left in your food budget for the month.

Handling unknown users​

If a WhatsApp message arrives from a phone number that does not match any registered user:

  • Ozzie returns 404 to Evolution API
  • No reply is sent to the user
  • The event is logged for debugging

To onboard users via WhatsApp, you need a separate registration flow. See the WhatsApp Bot use case guide for a complete implementation that handles first-time messages and triggers user registration.


Errors​

HTTP StatusWhen it occurs
200 OKMessage processed successfully β€” Ozzie has sent or queued a reply
401 UnauthorizedThe apikey header is missing or does not match EVOLUTION_WEBHOOK_SECRET
404 Not FoundNo Ozzie user found with a matching phone_e164
422 Unprocessable EntityThe payload is malformed or the messageType is not supported
500 Internal Server ErrorProcessing failed β€” Evolution API will retry depending on its configuration
tip

Evolution API typically retries failed webhook deliveries. Make sure your endpoint is idempotent β€” processing the same message twice should not create duplicate transactions. Ozzie deduplicates by the Evolution API message id field.