Context
PR #203 ships the full UI flow for sending optimized routes to drivers via WhatsApp (modal, phone validation, Next.js API route). The actual WhatsApp send in that PR is mock-only by design — the real API call is explicitly scoped as a follow-up.
Per our architecture decision and confirmation from Nurtekin (who manages the WhatsApp allowlist): the WhatsApp API call must live in the C++ Cloud Run backend, not the Next.js frontend. The do-app-service service account already has Secret Manager access on benevolent-bandwidth — no additional IAM setup needed.
What needs to be built
Add a new endpoint POST /api/whatsapp/send-route to the C++ backend (app/api/src/endpoints/whatsapp_endpoint.cpp) following the same Drogon patterns as osrm_proxy_endpoint.cpp.
Request body (matches what the Next.js route already sends):
```json
{
"to": "14155551234",
"message": "Your route for today: ..."
}
```
Implementation — credentials come from env vars, Cloud Run injects them from Secret Manager automatically:
```cpp
// Read credentials — same ResolveEnvOrDefault pattern used throughout the codebase
const auto access_token = ResolveEnvOrDefault("WHATSAPP_ACCESS_TOKEN", "");
const auto phone_number_id = ResolveEnvOrDefault("WHATSAPP_PHONE_NUMBER_ID", "");
// POST to WhatsApp API — same drogon::HttpClient pattern as osrm_proxy_endpoint.cpp
auto wa_client = drogon::HttpClient::newHttpClient("https://graph.facebook.com");
// In the handler:
auto upstream = drogon::HttpRequest::newHttpRequest();
upstream->setMethod(drogon::Post);
upstream->setPath("/v18.0/" + phone_number_id + "/messages");
upstream->addHeader("Authorization", "Bearer " + access_token);
upstream->setContentTypeCode(drogon::CT_APPLICATION_JSON);
Json::Value payload;
payload["messaging_product"] = "whatsapp";
payload["to"] = to_number; // from request body, no + prefix
payload["type"] = "text";
payload["text"]["body"] = message; // the formatted route text
upstream->setBody(payload.toStyledString());
```
Return the WhatsApp API response status back to the caller.
After this is merged
whatsappClient.ts in the Next.js app (app/ui/src/lib/whatsapp/whatsappClient.ts) needs to be updated to call this C++ endpoint instead of the current mock. That is a small follow-up change (~5 lines).
Acceptance criteria
Context
PR #203 ships the full UI flow for sending optimized routes to drivers via WhatsApp (modal, phone validation, Next.js API route). The actual WhatsApp send in that PR is mock-only by design — the real API call is explicitly scoped as a follow-up.
Per our architecture decision and confirmation from Nurtekin (who manages the WhatsApp allowlist): the WhatsApp API call must live in the C++ Cloud Run backend, not the Next.js frontend. The
do-app-serviceservice account already has Secret Manager access onbenevolent-bandwidth— no additional IAM setup needed.What needs to be built
Add a new endpoint
POST /api/whatsapp/send-routeto the C++ backend (app/api/src/endpoints/whatsapp_endpoint.cpp) following the same Drogon patterns asosrm_proxy_endpoint.cpp.Request body (matches what the Next.js route already sends):
```json
{
"to": "14155551234",
"message": "Your route for today: ..."
}
```
Implementation — credentials come from env vars, Cloud Run injects them from Secret Manager automatically:
```cpp
// Read credentials — same ResolveEnvOrDefault pattern used throughout the codebase
const auto access_token = ResolveEnvOrDefault("WHATSAPP_ACCESS_TOKEN", "");
const auto phone_number_id = ResolveEnvOrDefault("WHATSAPP_PHONE_NUMBER_ID", "");
// POST to WhatsApp API — same drogon::HttpClient pattern as osrm_proxy_endpoint.cpp
auto wa_client = drogon::HttpClient::newHttpClient("https://graph.facebook.com");
// In the handler:
auto upstream = drogon::HttpRequest::newHttpRequest();
upstream->setMethod(drogon::Post);
upstream->setPath("/v18.0/" + phone_number_id + "/messages");
upstream->addHeader("Authorization", "Bearer " + access_token);
upstream->setContentTypeCode(drogon::CT_APPLICATION_JSON);
Json::Value payload;
payload["messaging_product"] = "whatsapp";
payload["to"] = to_number; // from request body, no + prefix
payload["type"] = "text";
payload["text"]["body"] = message; // the formatted route text
upstream->setBody(payload.toStyledString());
```
Return the WhatsApp API response status back to the caller.
After this is merged
whatsappClient.tsin the Next.js app (app/ui/src/lib/whatsapp/whatsappClient.ts) needs to be updated to call this C++ endpoint instead of the current mock. That is a small follow-up change (~5 lines).Acceptance criteria
POST /api/whatsapp/send-routeexists on the C++ backendtoormessageare missing from the request bodyWHATSAPP_ACCESS_TOKENorWHATSAPP_PHONE_NUMBER_IDenv vars are not set