API Reference

Devices

List channels and quota, add new Device (QR) numbers, pair via QR or pairing code, reconnect, delete, and onboard Cloud API / Coexistence numbers through hosted sessions.

Two different onboarding paths Device / QR is fully API-driven (POST /devices → QR or pairing code → poll status). Cloud / Coexistence uses a Washeej-hosted Embedded Signup page (Facebook login + OTP + PIN) because Meta does not allow partners to embed that flow with Washeej's app credentials.

Prerequisites

  • Active plan with the right channel flags: device_channels_available for QR, cloud_api_available for Cloud / Coexistence.
  • Free shared account_limit slot (see quota on GET /devices; -1 = unlimited).
  • Scope devices:read to list/status, qr:generate for QR, devices:manage for create / pairing / reconnect / delete / default / hosted onboarding.
  • For Cloud hosted onboarding: register your callback host as a redirect URI under Dashboard → Developer Apps (required even with API-key auth).
GET /devices devices:read https://app.washeej.com/v1/devices

List all WhatsApp channels (Cloud + Device/QR) with the account quota.

cURL
curl -H "client-id: YOUR_CLIENT_ID" \
     -H "client-secret: YOUR_CLIENT_SECRET" \
     https://app.washeej.com/v1/devices
Response
{
    "ok": true,
    "data": {
        "devices": [
            {
                "id": 47,
                "business_name": "My Business",
                "phone_number": "966920000000",
                "channel_type": "cloud",
                "connection_status": "connected",
                "is_default": true,
                "origin": "cloud",
                "created_at": "2026-07-01T10:00:00+03:00"
            }
        ],
        "quota": {
            "account_limit_remaining": 3,
            "channels_in_use": 2,
            "cloud_api_available": true,
            "device_channels_available": true
        }
    },
    "meta": {
        "request_id": "uuid"
    }
}
Notes
  • quota.account_limit_remaining = -1 means unlimited.
  • origin is "cloud" for Cloud API / Coexistence numbers and "device" for QR device channels.
  • effective_channel_type resolves legacy rows where channel_type and origin disagree — prefer it over channel_type when routing logic.
  • Legacy keys (id, business_name, phone_number, channel_type, connection_status, last_seen_at, is_default) are stable; new fields are additive.
GET /devices/{id}/status devices:read https://app.washeej.com/v1/devices/{id}/status

Connection status for a channel (live-syncs Device/QR from GOWA).

Parameter Type Required Description
id path integer required WhatsApp account / device ID.
cURL
curl -H "client-id: YOUR_CLIENT_ID" \
     -H "client-secret: YOUR_CLIENT_SECRET" \
     https://app.washeej.com/v1/devices/YOUR_ACCOUNT_ID/status
Response
{
    "ok": true,
    "data": {
        "id": 52,
        "connection_status": "connected",
        "last_seen_at": "2026-07-18T14:12:41+03:00",
        "last_heartbeat_at": "2026-07-18T14:12:41+03:00",
        "last_error": null,
        "phone_number": "966500000000",
        "gowa_raw_status": "CONNECTED",
        "awaiting_link": false
    },
    "meta": {
        "request_id": "uuid"
    }
}
Notes
  • For Device/QR channels this endpoint refreshes status from the WhatsApp device service on every call — poll it after scanning a QR or entering a pairing code until connection_status is connected.
  • While the link is still in progress, connection_status stays "pending" and awaiting_link is true — even if the raw device service reports DISCONNECTED (gowa_raw_status). Do not treat pending as a failure; wait for connected.
  • A channel only becomes "disconnected" after it was connected and then lost the link.
  • Cloud / Coexistence channels return the stored status (no GOWA sync; gowa_raw_status is null).
  • Response also includes phone_number when known.
  • Tip: instead of polling you can subscribe to the device.connected / device.status_changed webhooks — they also fire when a transition is discovered by this endpoint.
POST /devices devices:manage https://app.washeej.com/v1/devices

Create a new Device (QR) channel within the plan quota.

Parameter Type Required Description
name string required Channel display name (max 50).
type string optional Only "device" is accepted (default). Cloud numbers use hosted onboarding sessions.
phone_number string optional Digits only, with country code. If the number already has a pending device channel, that channel is reused instead of creating a new one.
cURL
curl -X POST https://app.washeej.com/v1/devices \
  -H "client-id: YOUR_CLIENT_ID" \
  -H "client-secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "device",
    "name": "Store Front",
    "phone_number": "966500000000"
  }'
Response
{
    "ok": true,
    "data": {
        "device": {
            "id": 52,
            "business_name": "Store Front",
            "phone_number": "966500000000",
            "channel_type": "device",
            "connection_status": "pending",
            "is_default": false,
            "origin": "device"
        },
        "reused": false,
        "message": "Device channel created. Generate a QR or pairing code to connect."
    },
    "meta": {
        "request_id": "uuid"
    }
}
Notes
  • Requires device_channels_available on your plan and a free account_limit slot.
  • After creation, call POST /devices/{id}/qr (or /pairing-code) and poll GET /devices/{id}/status until connected.
  • A connected duplicate phone_number is rejected with duplicate_device (409).

Common errors: feature_disabled, limit_reached, duplicate_device, unsupported_type, gowa_error, validation_error

POST /devices/{id}/qr qr:generate https://app.washeej.com/v1/devices/{id}/qr

Generate or refresh a QR code for Device/QR pairing.

Parameter Type Required Description
id path integer required Device channel ID (channel_type=device).
cURL
curl -X POST https://app.washeej.com/v1/devices/YOUR_DEVICE_ID/qr \
  -H "client-id: YOUR_CLIENT_ID" \
  -H "client-secret: YOUR_CLIENT_SECRET"
Response
{
    "ok": true,
    "data": {
        "device_id": 52,
        "qr": "data:image/png;base64,iVBOR\u2026",
        "status": "PENDING",
        "connected": false,
        "qr_duration": 30
    },
    "meta": {
        "request_id": "uuid"
    }
}
Notes
  • Only works for Device/QR channels. Cloud accounts return invalid_channel_type.
  • qr is a data:image/png;base64 URI. Scan it with WhatsApp on the phone, then poll GET /devices/{id}/status until connected.
  • If the device is already connected, qr is null and connected is true.
  • The QR expires after qr_duration seconds — call this endpoint again to refresh it.

Common errors: invalid_channel_type, qr_failed, gowa_error, not_found

POST /devices/{id}/pairing-code devices:manage https://app.washeej.com/v1/devices/{id}/pairing-code

Generate a text pairing code (alternative to scanning a QR).

Parameter Type Required Description
id path integer required Device channel ID.
phone string required Phone number with country code (digits). Alias: phone_number.
cURL
curl -X POST https://app.washeej.com/v1/devices/YOUR_DEVICE_ID/pairing-code \
  -H "client-id: YOUR_CLIENT_ID" \
  -H "client-secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"phone": "966500000000"}'
Response
{
    "ok": true,
    "data": {
        "device_id": 52,
        "pairing_code": "ABCD-1234",
        "phone_number": "966500000000",
        "message": "Pairing code generated. Enter it in WhatsApp on the phone."
    },
    "meta": {
        "request_id": "uuid"
    }
}
Notes
  • The user enters the code in WhatsApp → Linked Devices → Link with phone number instead.
  • Poll GET /devices/{id}/status until connected (status live-syncs from GOWA).
  • If pairing fails, fall back to POST /devices/{id}/qr.
  • Accepts either phone or phone_number in the JSON body.

Common errors: invalid_channel_type, pairing_failed, gowa_error, validation_error, not_found

POST /devices/{id}/reconnect devices:manage https://app.washeej.com/v1/devices/{id}/reconnect

Reset a disconnected/orphaned device so it can be re-paired.

Parameter Type Required Description
id path integer required Device channel ID.
cURL
curl -X POST https://app.washeej.com/v1/devices/YOUR_DEVICE_ID/reconnect \
  -H "client-id: YOUR_CLIENT_ID" \
  -H "client-secret: YOUR_CLIENT_SECRET"
Notes
  • After reconnect, generate a fresh QR or pairing code to complete the connection.

Common errors: invalid_channel_type, not_found

POST /devices/{id}/default devices:manage https://app.washeej.com/v1/devices/{id}/default

Set a channel (any type) as the account default for sending.

Parameter Type Required Description
id path integer required Channel ID.
cURL
curl -X POST https://app.washeej.com/v1/devices/YOUR_DEVICE_ID/default \
  -H "client-id: YOUR_CLIENT_ID" \
  -H "client-secret: YOUR_CLIENT_SECRET"

Common errors: not_found

DELETE /devices/{id} devices:manage https://app.washeej.com/v1/devices/{id}

Delete any channel (Device, Cloud, Coexistence); frees one quota slot.

Parameter Type Required Description
id path integer required Channel ID (any type).
cURL
curl -X DELETE https://app.washeej.com/v1/devices/YOUR_DEVICE_ID \
  -H "client-id: YOUR_CLIENT_ID" \
  -H "client-secret: YOUR_CLIENT_SECRET"
Response
{
    "ok": true,
    "data": {
        "deleted": true,
        "channel_type": "cloud",
        "quota_restored": true,
        "device_id": 52
    },
    "meta": {
        "request_id": "uuid"
    }
}
Notes
  • DESTRUCTIVE AND IRREVERSIBLE: deleting a channel also deletes all conversations, messages, templates, and campaigns linked to it — the same cascade as deleting the number from the dashboard.
  • Works for every channel type: Device (QR), Cloud, and Coexistence.
  • Device channels are logged out from the WhatsApp device service first (best-effort).
  • One account_limit slot is restored on success (unless the plan is unlimited) — quota_restored in the response tells you which.
  • If the deleted channel was the default, the first remaining channel becomes the new default.

Common errors: not_found, server_error

POST /devices/cloud/onboarding-sessions devices:manage https://app.washeej.com/v1/devices/cloud/onboarding-sessions

Create a hosted onboarding session to add a Cloud API (or Coexistence) number.

Parameter Type Required Description
return_url url required HTTPS URL on your platform to receive the user back. Its host must be registered as a redirect URI on your developer app.
state string optional Opaque CSRF token (max 128 chars) echoed back on the redirect.
mode string optional "cloud" (default) or "coexistence" (keeps the number active in the WhatsApp Business App; requires the feature to be enabled).
developer_app_id integer optional When using API keys (no OAuth app context), pin the return_url allowlist to one Developer App instead of the union of all apps on the account. Ignored for OAuth Bearer tokens (bound automatically).
cURL
curl -X POST https://app.washeej.com/v1/devices/cloud/onboarding-sessions \
  -H "client-id: YOUR_CLIENT_ID" \
  -H "client-secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "return_url": "https://partner.example.com/washeej/callback",
    "state": "random-csrf-token"
  }'
Response
{
    "ok": true,
    "data": {
        "session_id": "9c2f1a4e-1234-4bcd-9e0f-abcdef012345",
        "mode": "cloud",
        "hosted_url": "https://app.washeej.com/user/whatsapp-onboarding/9c2f1a4e-\u2026",
        "expires_at": "2026-07-18T14:30:00+03:00",
        "status": "pending"
    },
    "meta": {
        "request_id": "uuid"
    }
}
Notes
  • Prerequisites: (1) Register your callback host as a redirect URI on a Developer App in the Washeej Developer Portal — required even when authenticating with API keys. (2) Grant scope devices:manage. (3) Plan must have cloud_api_available and a free account_limit slot.
  • Redirect your user to hosted_url in a full browser window (not an iframe — Meta Embedded Signup blocks framing).
  • The end user must sign in to the same Washeej account that owns the API credentials. A different account receives HTTP 403.
  • Washeej hosts Meta Embedded Signup (Facebook login, portfolio + number selection, OTP, PIN). Meta app secrets and access tokens never reach your platform.
  • When onboarding ends, the user is redirected to return_url?session_id=…&status=completed|failed|expired&state=…&device_id=… — always confirm with GET /devices/cloud/onboarding-sessions/{id} server-side; never trust the redirect alone.
  • Sessions expire after 30 minutes. If the number finishes connecting just after expiry, Washeej still finalizes the session so you receive device_id.
  • return_url must be HTTPS (HTTP allowed only for localhost / 127.0.0.1). Only the host is allowlisted — path may differ from the registered redirect URI.
  • Coexistence mode (mode=coexistence): number stays active in the WhatsApp Business App (no PIN). Contact & chat history sync must complete within Meta's 24-hour window; sending via API may be limited until sync finishes.

Common errors: feature_disabled, limit_reached, return_url_not_allowed, validation_error

GET /devices/cloud/onboarding-sessions/{id} devices:manage https://app.washeej.com/v1/devices/cloud/onboarding-sessions/{id}

Check a hosted onboarding session status; returns the device on completion.

Parameter Type Required Description
id path uuid required Session ID from the create call.
cURL
curl -H "client-id: YOUR_CLIENT_ID" \
     -H "client-secret: YOUR_CLIENT_SECRET" \
     https://app.washeej.com/v1/devices/cloud/onboarding-sessions/SESSION_ID
Response
{
    "ok": true,
    "data": {
        "session_id": "9c2f1a4e-1234-4bcd-9e0f-abcdef012345",
        "mode": "cloud",
        "status": "completed",
        "expires_at": "2026-07-18T14:30:00+03:00",
        "completed_at": "2026-07-18T14:12:41+03:00",
        "device": {
            "id": 61,
            "business_name": "My Business",
            "phone_number": "966920000000",
            "channel_type": "cloud",
            "connection_status": "connected",
            "is_default": false,
            "origin": "cloud"
        }
    },
    "meta": {
        "request_id": "uuid"
    }
}

Common errors: not_found

QR pairing flow (Device channels)

1
Create the channelPOST /devices with type: device — requires scope devices:manage and a free quota slot. Optional phone_number reuses a pending record for the same digits.
2
Generate QR or pairing codePOST /devices/{id}/qr (scope qr:generate) or POST /devices/{id}/pairing-code with {"phone":"9665…"}.
3
Scan or enter the codeWhatsApp → Linked devices → scan the QR, or “Link with phone number instead” for the pairing code.
4
ConfirmPoll GET /devices/{id}/status every 2–3s until connection_status is connected. While the link is in progress the status stays pending with awaiting_link: true — it never flips to disconnected before the first successful link. Or subscribe to the device.connected webhook instead of polling.

Cloud / Coexistence flow (hosted onboarding)

1
Register redirect hostDeveloper Portal → Developer Apps → add e.g. https://partner.example.com/washeej/callback (host is what matters).
2
Create a sessionPOST /devices/cloud/onboarding-sessions with return_url, optional state, optional mode (cloud | coexistence).
3
Redirect the userOpen hosted_url in a top-level window. User signs into Washeej (same account as the API), then Meta Embedded Signup.
4
Receive + verifyUser returns to return_url?session_id&status&state&device_id. Confirm with GET /devices/cloud/onboarding-sessions/{id} — never trust the query string alone.

Common scenarios

ScenarioBehaviour
Create with a phone already connected 409 duplicate_device — use reconnect or a different number.
Create with a phone that has a pending/disconnected channel 200 with reused: true — no extra quota consumed; generate QR/pairing again.
POST /devices with type: cloud 422 unsupported_type — use hosted onboarding sessions.
QR/pairing/reconnect on a Cloud ID 422 invalid_channel_type.
Polling status while QR not yet scanned connection_status: pending, awaiting_link: true (raw GOWA state in gowa_raw_status) — not a failure.
QR on an already connected device qr: null, connected: true.
Plan without Device Channels 403 feature_disabled on create.
Quota exhausted (account_limit = 0) 403 limit_reached.
return_url host not registered 422 return_url_not_allowed with allowed_hosts.
API key without devices:manage in explicit scopes 403 insufficient_scope on manage endpoints. Keys with scopes = null keep all scopes (including manage).
Delete any channel (Device, Cloud, Coexistence) Irreversible cascade: conversations, messages, templates, and campaigns tied to the channel are deleted (GOWA logout first for Device). One account_limit slot restored — see quota_restored.

Compatibility notes

  • GET /devices, GET /devices/{id}/status, and POST /devices/{id}/qr keep their legacy response keys. New fields (quota, origin, effective_channel_type, connected, qr_duration, phone_number / gowa_raw_status / awaiting_link on status) are additive.
  • Legacy /device-api/v1 and /external-api are unchanged.
  • Webhooks: device.connected / device.disconnected fire for Device channels (unchanged), and the new device.status_changed fires on every transition for all channel types with from_status / to_status. Transitions discovered by the status poll also fire these events — see Webhook payloads.
  • DELETE /devices/{id} now accepts every channel type (previously Device only). Existing Device-only integrations are unaffected.