Skip to content
Back to projects

WhatsApp chatbot · n8n

Ajuda Animal: animal abuse reports over WhatsApp

A WhatsApp channel for reporting animal abuse, in an IFSP extension project with the Jacareí city government. I built the n8n bot and its integration with the system's API.

Private code: university extension project in partnership with the city government. Happy to walk through it in an interview.

My role
WhatsApp channel: n8n bot, API integration and conversation sessions
Period
2026 · IFSP Jacareí extension project
Status
in progress
Stack
n8n · Evolution API · PostgreSQL · Docker
End-to-end demo from 10/19/2026 (1 min 33 s, no sound, in Portuguese). Addresses and coordinates are blurred.

30-second summary

Problem
The city's Animal Protection department got reports through calls and loose messages, with no location, no photo and no way to follow up.
Solution
A WhatsApp bot walks the citizen through the report (type, animal, location, photo and anonymity), saves it through the system's API and replies with a protocol number.
Result
A working prototype, demoed end to end on 10/19/2026: the report lands on the team's map and the citizen follows it by protocol. Not in production yet.

Context and problem

Ajuda Animal ("Animal Help") is an extension project of IFSP Jacareí (a federal institute) with the Jacareí city government, for its Animal Protection department. It's a team project: a system to receive and follow up reports of animal abuse and abandonment.

The department needs reports with useful data to act on: where the animal is, what is happening and a photo. Calls and loose messages arrive incomplete and can't be followed up. WhatsApp is the channel people already use, but a free-form chat doesn't produce structured data.

My role

I was responsible for the WhatsApp channel: the n8n chatbot, its connection to the system's back end (core-api) and the conversation session database. I also carried the data and photos from WhatsApp into the system, where the report is created. The dashboard and the API belong to the rest of the team.

How it works

My part: the WhatsApp channel

CitizenOn WhatsApp, nothing to install
Evolution APIReceives and sends messages
n8nRuns the conversation
reads and writes
whatsapp_sessionsStep and answers for each number
HTTP + x-service-key

The team's system

core-apiNestJS + Prisma: protocol and validation
JWT
Web dashboardReact + Leaflet: the inspection map
PostgreSQL + PostGISReports with coordinates
The bot only writes through the system's API. The conversation state lives in a separate database.

The conversation is a state machine. Each phone number has a row in the whatsapp_sessions table, with the current step and the partial answers.

  1. 01Menu1, 2 or 3
  2. 02Type5 options
  3. 03AnimalFree text
  4. 04LocationPin or address
  5. 05PhotoOr SKIP
  6. 06PrivacyAnonymous or with contact
  7. 07ProtocolPOST /reports
Report steps, stored in current_step.

In the database, the steps are INICIO, MENU, DENUNCIA_TIPO, DENUNCIA_ANIMAL, DENUNCIA_LOCAL, DENUNCIA_FOTO and DENUNCIA_LGPD. The menu's other branch is CONSULTA_PROTOCOLO.

  • Menu and exits. 1. Report · 2. Check a protocol · 3. General guidance. menu, voltar (back) or cancelar (cancel) go back to the menu at any point.
  • Normalized input. The workflow only handles incoming messages (it ignores groups, status updates and its own messages) and extracts the text, photo caption, location and phone number.
  • Location. The citizen shares the spot on WhatsApp and the coordinates go into the report, which lands on the map. A typed address works too.
  • Mapping to the API contract. A code node builds the POST /reports body from the session. The bot's 5 options become ABUSE or ABANDONMENT (the detail goes in the description), and the free-text animal ("caramel dog", "rabbit") becomes a species through keywords.
  • Protocol. The API generates a readable code like DEN-2026-7K3QX9, with no 0/O or 1/I/L so citizens don't mix them up, and the bot sends it right away.
  • Lookup. The citizen types the protocol (case, spaces and dashes don't matter) and gets the type, status and last update date. No address, description or contact details.
  • Errors. If the API fails, the bot says so, keeps the answers and lets the citizen try again.

Try it

Chat with a replica of the bot I built in n8n: file a report, get the protocol and check its progress. Alongside, see each message's path through the workflow, the session in the database, the API call and the report on the city team's dashboard.

Simulation with fictional data · no WhatsApp, no database

What matches the bot and what is simulated

  • Same as the bot: the state machine (including what the workflow accepts at each step), the menu, back, cancel and reset commands, how the POST /reports body is built, the protocol format and the normalization in the lookup.
  • Simulated: there is no WhatsApp, n8n or database. Everything runs in your browser and is gone when the page reloads. The phone number, places and photo are fictional.
  • The "Simulate an API failure" box shows the workflow's error messages: the answers are kept so you can try again.
  • The "Advance status" button plays the team's role on the dashboard; the protocol lookup shows the change.
  • The original bot is in Portuguese. This version translates its messages, the commands and the species keywords, and drops the emojis.

Stack

  • My work: n8n, Evolution API (WhatsApp), PostgreSQL, REST integration with a service key, and Docker.
  • The system: monorepo (pnpm + Turborepo), NestJS + Prisma + PostgreSQL/PostGIS API, React + Vite + Leaflet dashboard, Docker Compose and CI on GitHub Actions.

Decisions and trade-offs

The bot doesn't write to the system's database

Only the API touches the business database. n8n uses the same REST endpoints an official gateway would, authenticated with a service key. That keeps the rules that matter (protocol, anonymity, validation) in the API, which is tested. The conversation state lives in a separate table.

trade-offThe n8n workflow sits outside CI (lint and tests), so no important rule can live in it.

n8n and Evolution API for the prototype, recorded in an ADR

The official channel (Meta's WhatsApp Business) depends on the city government. To demo the full flow without waiting for Meta's verification, n8n plays the gateway's role. Since the API contract is the same, switching channels later doesn't change the back end or the dashboard.

trade-offEvolution uses an unofficial WhatsApp connection: test SIM only, never the city's number.

Privacy law (LGPD) from the start

Reports are anonymous by default: contact details are only kept if the citizen picks option 2. The protocol lookup exposes no personal data, the photo is stored as a reference (never as base64 in the session), and there is a ready query to delete sessions abandoned for over 30 days, still to be scheduled.

Result

A working prototype, demoed end to end on 10/19/2026 (the video at the top):

  • a full report over WhatsApp (type, animal, location, photo and privacy);
  • a protocol generated and sent right away;
  • the report on the dashboard map, with the citizen's photo;
  • progress updated on the dashboard shows up in the WhatsApp lookup.

The project's next steps: the official channel (Meta's WhatsApp Business), the city's server and an automatic alert to inspectors for every new report. The node for that alert is already in the workflow, but disabled.

What I learned

  • Integrating through a contract (API + service key) instead of going straight to the database lets you swap the channel without touching the rest of the system.
  • A public service chatbot has to be predictable: a numbered menu, exit commands and error messages that don't lose what the citizen already answered.
  • Privacy is an architecture decision, not an interface detail.