Prepare a QR-linked session and server-side credentials
RelayWA is a QR-linked WhatsApp Web session API, not Meta’s official WhatsApp Cloud API. First create an account, connect one WhatsApp number through Linked devices, and issue a session-bound API key with messages.send scope.
Install Node.js 20 or newer. Store RELAYWA_SESSION_KEY and RELAYWA_WEBHOOK_SECRET in your server environment, not browser JavaScript, public repositories, or mobile app bundles. Ask recipients for meaningful opt-in and respect applicable WhatsApp account rules.
- Send endpoint: POST https://relaywa.com/api/send-message
- Required JSON: to (international digits), text, and a stable clientMessageId for each business event
- Send and webhook response are transport events, not proof a recipient read the message
Send an order update with a stable idempotency key
Your app must make the call from a trusted server. The runnable example has a createRelayWaClient function that validates inputs and accepts an injectable fetch implementation for offline tests.
Use an order-specific clientMessageId such as order-123-ready-v1. The API stores it per session to reduce duplicate message creation. If the request times out, delivery may already be in progress: look up the message or retry only with the same key after checking current status.
const sendText = createRelayWaClient({ sessionKey: process.env.RELAYWA_SESSION_KEY });
await sendText({
to: "12025550123",
text: "Your order #123 is ready.",
clientMessageId: "order-123-ready-v1"
});Verify signed webhooks before processing events
Configure a public HTTPS webhook in your workspace. RelayWA signs the exact raw HTTP request body with HMAC-SHA256 using the secret returned during webhook creation, preceded by the x-relaywa-timestamp and a period. It sends x-relaywa-signature in sha256=HEX format.
Do not parse and then reserialize JSON before hashing: whitespace and key order matter. Use crypto.timingSafeEqual for the 32-byte digest and reject stale timestamps to limit replay. Persist event.id with a database UNIQUE constraint and acknowledge duplicates before scheduling downstream processing.
const computed = createHmac("sha256", webhookSecret)
.update(timestamp).update(".").update(rawBodyBuffer).digest();
const valid = submitted.length === computed.length &&
timingSafeEqual(submitted, computed);Handle failures without promising guaranteed delivery
401 signals invalid credentials, 403 indicates a missing scope or authorization, and 409 can indicate a disconnected session. In these cases, fix the cause before sending again. Transport errors or 503 can have an uncertain outcome; use the returned message ID when available and inspect session/message status.
RelayWA does not operate an outbound Redis queue or automatically retry failed outbound sends. Scheduling, pacing, and bounded retry backoff belong in your application. Webhook delivery retry behavior is separate from outgoing message retry behavior.
- Test with a connected number you control and consenting recipient
- Run the included Node fixture tests locally or in GitHub CI before adapting the code
- Never promise ban immunity, guaranteed recipient delivery, or official Meta authorization