Agent Provisioning
How an ESP32 running the WakeLink firmware gets its Wi-Fi credentials, its end-to-end EWSP agent token, and its per-agent relay token — using the firmware's built-in AP setup portal. Your master account token never leaves your phone.
/api/* consumed by the Android setup wizard (the primary path), and a browser-friendly HTML form on GET / kept as a fallback for desktop users. Both share the same NVS AP password and write to the same wakelink namespace. Three secrets, three scopes
Provisioning binds three unrelated secrets to the agent. Conflating them is the #1 source of configuration bugs.
EWSP agent token
- End-to-end secret shared only between your client and your ESP. The relay never sees it.
- You choose its value during provisioning — anything ≥ 32 random characters. Generate with
openssl rand -hex 16. - EWSP session keys are HKDF-derived from this token, then used with an XChaCha20-Poly1305 AEAD on every wake packet.
- Stored in the
wakelinkNVS namespace as plaintext (never transmitted to server).
Per-agent relay token (wla_…)
- Generated by the relay when you register an agent via the Android app or CLI. Format:
wla_<64 hex chars>. - Stored as a SHA-256 hash in the relay's
agentstable; the ESP32 stores the plaintext in NVS. - Used by the firmware to authenticate its WebSocket session to the relay, routing packets to the right agent.
- Your master account token (
wl_…) is never stored on the ESP32. The Android app uses it once to create the agent and receive thiswla_token, then passes it to the agent.
wl_…): This is your user-level API token, used by the Android app and CLI to call the relay REST API. It is never provisioned to or stored on the ESP32 hardware. When you create an agent via POST /api/v1/agents/, the relay returns a fresh wla_… agent relay token — that is what the app then sends to the ESP32. AP portal flow
When the firmware boots without saved credentials it starts a soft-AP and waits for you on a tiny HTML form served on its own subnet.
1 ESP boots into provisioning mode
WakeLink-Setup as a soft-AP at 192.168.4.1. On first boot it generates a random 8-character AP password via esp_random() and persists it in NVS (namespace wl_prov, key ap_pass). 2 Retrieve the AP password from NVS
esptool.py read_flash and parse it with the ESP-IDF nvs_partition_gen.py tool. 3 Join the AP from your phone or laptop
WakeLink-Setup using the password from NVS. Your client should auto-receive 192.168.4.x via DHCP. 4 Open the form at http://192.168.4.1/
/api/*. The Android wizard targets the JSON API. 5 Submit credentials (form) or run the wizard (JSON)
JSON API (wizard path)
The provisioning JSON API is what the Android setup wizard speaks. Every endpoint accepts and returns application/json. Authentication is a two-step CSRF-token flow rooted in the NVS-stored AP password.
POST /api/login with {"ap_password":"<8 chars>"} to receive a csrf_token. Every subsequent /api/* call must include X-CSRF-Token: <token>. Endpoints
| Endpoint | Description |
|---|---|
| POST /api/login | Obtain CSRF token. Returns {ok,csrf_token,agent_info:{chip,mac,fw_version}} + Set-Cookie: wl_verified=1. |
| GET /api/info | Returns {chip,mac,fw_version,provisioned,agent_id,...}. |
| GET /api/scan | Runs a live Wi-Fi scan. Returns {networks:[{ssid,rssi,encryption,bssid,channel,...}]}. |
| POST /api/wifi | Body: {ssid,password}. Tests connection with a 15 s timeout. |
| POST /api/save | Writes any subset of NVS fields: ssid, password, server_host, server_port, tls_enabled, agent_id, agent_token, api_token. |
| POST /api/save-and-restart | Same as /api/save plus a reboot 500 ms later. |
| POST /api/restart | Reboot in 500 ms. |
| POST /api/factory-reset | Wipes both wakelink and wl_prov NVS namespaces, then reboots. |
{\`# End-to-end wizard call sequence
curl -X POST http://192.168.4.1/api/login \\
-H 'Content-Type: application/json' \\
-d '{"ap_password":"AbCdEfGh"}'\`}HTML form fallback
The form posts application/x-www-form-urlencoded to POST /save. The CSRF token is embedded in the rendered HTML page.
| Field | Required | Description |
|---|---|---|
| wifi_ssid | Yes | Your Wi-Fi network name. |
| wifi_pass | Wi-Fi password. | |
| server_host | Relay host, e.g. wakelink-project.org. | |
| server_port | Defaults to 443. | |
| tls_enabled | Checkbox. Automatically forced on for port 443. | |
| agent_id | Yes | Public agent identifier, format WL<8 hex>. |
| agent_token | Yes | Your EWSP secret. The relay never sees this. |
| api_token | Yes | Your per-agent relay token (wla_...). Not your master account token. |
Rotation & re-provisioning
Reset to AP mode
Hold the firmware's reset button for 10 seconds — saved credentials in the wakelink NVS namespace are wiped.
Rotate EWSP agent token
Reset to AP mode, re-provision with a new agent_token. The relay is not involved — both ends only need to share the same secret.
Rotate per-agent relay token (wla_…)
Call POST /api/v1/agents/{id}/rotate-token from the Android app or CLI. Reset the ESP32 to AP mode and re-provision with the new value.
Rotate master account token (wl_…)
Regenerate it in the dashboard via POST /api/v1/auth/regenerate-token. The ESP32 does not need re-provisioning — it stores wld, not your master token.
Why this is safe
End-to-end encryption
Every wake payload is sealed by the EWSP layer derived from agent_token before it ever leaves your client.
Blind relay
The server only forwards opaque ciphertext. It never holds the EWSP agent token and cannot decrypt payloads.
Master token isolation
Your wl_… master account token never reaches the ESP32 hardware.
Local-only AP setup
The setup portal is reachable only from the ESP's own subnet (~10 m range). It shuts down the moment a config is saved.