This document describes the internal WhatsApp foundation for Salgadin.
The current implementation does not integrate with Meta WhatsApp Cloud API or Twilio. It only prepares the backend architecture and provides a local simulation endpoint for validating the future flow.
The simulator is an internal testing tool. It is not a public-facing WhatsApp feature and it does not send real messages.
Allow a user to register small daily expenses from messages such as:
Adicionar 50 em almoçoAdicionar 50 reais em almoçoGastei 25 com UberPaguei 120 no mercado50 caféMercado 89,90
The EF Core migration creates:
UserWhatsAppAccounts- links a Salgadin user to a normalized phone number.
WhatsAppLinkCodes- stores temporary connection codes such as
SALGADIN-482913.
- stores temporary connection codes such as
WhatsAppProcessedMessages- stores provider message IDs for idempotency.
EF Core migrations remain the source of truth for this schema.
All endpoints below require the normal Salgadin JWT.
POST /api/whatsapp/link-code
Authorization: Bearer <token>Response:
{
"code": "SALGADIN-482913",
"expiresAt": "2026-05-05T23:15:00Z"
}Current MVP behavior:
- the endpoint requires the user to have a valid phone number saved in Profile;
- the endpoint creates or reactivates the local
UserWhatsAppAccountfor simulation; - the code expires in 15 minutes;
- the code is not sent to Meta/Twilio yet.
GET /api/whatsapp/status
Authorization: Bearer <token>Response:
{
"connected": true,
"phoneNumber": "+5531999999999"
}DELETE /api/whatsapp/disconnect
Authorization: Bearer <token>This deactivates the active WhatsApp link for the authenticated user.
The simulation endpoint is available only in Development by default.
It can be explicitly enabled outside Development with:
WhatsApp__EnableSimulationEndpoint=true
Outside Development, enabling the endpoint is not enough. The caller must also be authenticated with the normal Salgadin JWT and the authenticated email must be present in:
WhatsApp__SimulatorAllowedEmails=email1@example.com,email2@example.com
Use this only for controlled testing. Do not expose broad access in production.
Frontend visibility is controlled separately:
VITE_ENABLE_WHATSAPP_SIMULATOR=true
For production or staging tests, all three settings must be coherent:
VITE_ENABLE_WHATSAPP_SIMULATOR=trueshows the panel in the frontend.WhatsApp__EnableSimulationEndpoint=trueenables the backend endpoint outside Development.WhatsApp__SimulatorAllowedEmails=...defines which authenticated users can call it.
The allowed email list is backend-only and must not be exposed to the frontend. The backend controller uses manual authorization checks for this endpoint, so the environment flag and allowlist must remain aligned with tests and documentation.
POST /api/dev/whatsapp/simulate
Content-Type: application/json
{
"from": "+5531999999999",
"text": "Adicionar 50 em almoço",
"messageId": "test-001"
}Success response:
{
"reply": "Despesa adicionada: R$ 50,00 em almoço."
}Duplicate message response:
{
"reply": "Mensagem ja processada. Nenhuma despesa duplicada foi criada."
}Unlinked phone response:
{
"reply": "Este telefone ainda nao esta conectado ao Salgadin."
}Unauthorized simulator user response:
403 ForbiddenThe frontend should show a friendly message such as:
Seu usuário não está autorizado a usar o simulador WhatsApp neste ambiente.
Other common environment responses:
401 Unauthorized- authenticated session is required outside Development
404 Not Found- simulation endpoint is not enabled in the current environment
The parser extracts:
amountdescriptioninferredCategoryNamedate, using current UTC time
Supported amount formats:
5089,9089.90
Ignored words:
adicionargasteipagueireaisrealemcomnona
Category inference:
Alimentacao: almoço, jantar, café, lanche, mercado, padaria, ifood, restauranteTransporte: uber, ônibus, gasolina, transporte, estacionamentoLazer: cinema, netflix, show, jogoOutros: fallback
The service tries to find a matching user category by name. If the inferred category does not exist, it tries Outros or Other. It does not create categories automatically.
messageId is stored in WhatsAppProcessedMessages.ProviderMessageId.
If the same message ID is received again, the service returns a duplicate response and does not create another expense.
- Start the API in Development.
- Log in normally and save a valid phone number in Profile.
- Generate a link code:
Invoke-RestMethod `
-Method Post `
-Uri http://localhost:5297/api/whatsapp/link-code `
-Headers @{ Authorization = "Bearer <token>" }- Simulate a message:
Invoke-RestMethod `
-Method Post `
-Uri http://localhost:5297/api/dev/whatsapp/simulate `
-ContentType "application/json" `
-Body '{"from":"+5531999999999","text":"Adicionar 50 em almoço","messageId":"test-001"}'Use the normalized phone returned by /api/whatsapp/status as from.
Next steps for a real integration:
- Add provider webhook signature validation.
- Add provider-specific message DTO mapping.
- Replace
/api/dev/whatsapp/simulatewith a secured webhook endpoint. - Use
WhatsAppLinkCodesto confirm the first message from a real phone. - Store provider message IDs in
WhatsAppProcessedMessages. - Add conversation state only if multi-step flows become necessary.