feat(api): add WhatsApp send route endpoint#215
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
markboenigk
left a comment
There was a problem hiding this comment.
Review
Good structure and test coverage — the four integration test scripts cover the happy path, validation errors, missing credentials, and upstream failure cleanly. Three issues need addressing before merge.
🔴 Whitespace env var silently bypasses the 503 guard
app/api/src/endpoints/whatsapp_endpoint.cpp lines 104–106
ResolveEnvOrDefault only checks for a null or zero-length string. A value like " " (a trailing newline from Secret Manager, or a space from a copy-paste into Cloud Run's env-var field) is not .empty(), so the access_token.empty() || phone_number_id.empty() guard evaluates false. The handler proceeds and sends Authorization: Bearer to Meta, which returns error 190. The caller gets a 502 "WhatsApp upstream request failed" with no hint the service is misconfigured — it looks like a transient Meta outage.
Fix: Apply the same std::all_of(…std::isspace…) check already used in ReadNonEmptyStringMember when reading the credentials, or trim the env var values before the empty check.
🔴 Graph API version v18.0 is hardcoded with no override
app/api/src/endpoints/whatsapp_endpoint.cpp line 119
upstream_request->setPath("/v18.0/" + phone_number_id + "/messages");The base URL gets ResolveNormalizedUrlEnvOrDefault so it can be swapped for tests; the API version cannot be changed without a code edit and redeploy. Meta v18.0 was released September 2023 on a 2-year deprecation cycle — its end-of-life is approximately September 2025. Once Meta enforces the sunset for this app, every send will return OAuthException code 2635 and WhatsApp notifications fail permanently with no runtime fix.
Fix: At minimum, extract a named constant (constexpr char kWhatsAppApiVersion[] = "v18.0"). Ideally add a WHATSAPP_API_VERSION env-var override consistent with kWhatsAppBaseUrlEnv, and bump the default to the current stable version (v21.0+).
🟡 Any caller who can reach the Cloud Run URL can trigger sends
app/api/src/endpoints/whatsapp_endpoint.cpp line 89
There is no application-layer auth on this handler — no Drogon filter, no token check in the lambda. This is consistent with the rest of the endpoints in this server, but the external impact here is qualitatively different: a POST from any caller sends a real WhatsApp message from the business's registered number to an arbitrary phone number, spending Meta API quota. Whether Cloud Run IAM is currently blocking allUsers is not visible from the code.
Worth confirming the Cloud Run service requires authentication (roles/run.invoker not granted to allUsers) before deploying, or adding an HMAC/shared-secret check on this endpoint specifically.
🔵 Nit: phone_number_id is not whitespace-trimmed before URL construction
app/api/src/endpoints/whatsapp_endpoint.cpp line 119
A trailing newline in WHATSAPP_PHONE_NUMBER_ID (common in Secret Manager exports) produces a malformed HTTP request line that Meta silently rejects. A whitespace trim on the env var value (same fix as the first finding above) covers this case.
Summary
Motivation
Changes
POST /api/whatsapp/send-routewith request validation, credential checks, and Meta Graph API POST handling.Validation
git diff --checkbash -n tests/integration/http_server/whatsapp_send_route_forwards_to_upstream_test.sh tests/integration/http_server/whatsapp_send_route_rejects_invalid_request_test.sh tests/integration/http_server/whatsapp_send_route_requires_configuration_test.sh tests/integration/http_server/whatsapp_send_route_returns_502_for_upstream_failure_test.shcmake --preset devblocked locally because CMake could not findjsoncppConfig.cmake/jsoncpp-config.cmake.clang-format -i app/api/src/endpoints/whatsapp_endpoint.cpp app/api/include/deliveryoptimizer/api/endpoints/whatsapp_endpoint.hppblocked locally becauseclang-formatis not installed in this shell.Risk
WHATSAPP_ACCESS_TOKENandWHATSAPP_PHONE_NUMBER_IDsecrets.Rollout and Recovery