DejaVPN for Providers
Integration guide · for app version 2.1 and later
1. The subscription link
Each user gets one link, unique to them:
https://your-domain.com/open/<token>
- HTTPS only. Plain HTTP links are refused.
- The path must be
/open/<token>. Links written as/sub/<token>are accepted too and rewritten to/open/<token>, so serve the same answer on both if your panel already uses/sub/. - The token identifies the user. Treat it like a password: anyone holding the link gets the user's servers.
Users add the link by pasting it, scanning it as a QR code, or tapping a one-tap link on your website or bot:
dejavpn://add?url=https%3A%2F%2Fyour-domain.com%2Fopen%2F<token>
The url parameter is the subscription link, URL-encoded. On a device with DejaVPN
installed, the link opens the app with the subscription ready to add.
2. What DejaVPN sends
The app requests the link with GET when the user adds it, when the app opens (if the
user keeps "Update servers on open" on), when they tap Update Now, and before connecting.
Every request carries:
| Header | Example | Meaning |
|---|---|---|
x-hwid | a3f9… | Device ID: a one-way hash, stable per device and app install. Use it for device limits. |
x-device-os | Android | Android, Windows, Linux or iOS. |
x-ver-os | 15 | Operating system version. |
x-device-model | Pixel 9 | Device model, for showing the user their devices. |
User-Agent | DejaVPN/2.1.4 (Android 15) | App and version. |
These are the same header names Happ uses, so panels that already support Happ's device limits
understand them without changes. On Android, the same device ID is also sent to your OpenVPN
server in the handshake as UV_HWID, so an auth-user-pass-verify script can check it too.
3. What to answer
Status codes
| Code | What DejaVPN does |
|---|---|
200 | Reads the servers below and replaces the saved list (new servers appear, removed ones go, changed addresses update). |
403 | Plan not usable right now. Keeps the saved servers but shows why, from subscription-status: limited, expired, device limit or disabled. |
404 / 410 | Subscription deleted. Shown as deleted; the user can remove it. |
5xx, timeout | Temporary problem. Keeps the saved servers and tries again later. |
Response body (JSON)
{
"servers": [
{
"id": "de-fra-1",
"name": "Germany · Frankfurt",
"country": "DE",
"host": "203.0.113.10:1194",
"ovpnConfig": "client\ndev tun\nproto udp\nremote 203.0.113.10 1194\n…",
"username": "user123",
"password": "secret"
}
]
}
| Field | Meaning | |
|---|---|---|
servers | required | The user's servers. Order is kept. |
id | recommended | Stable ID per server. Keep it the same when the address changes, so the app knows it's the same server (and keeps the user's choice). |
name | recommended | Shown in the list. Starting with a country name ("Germany · Frankfurt") also gives it a flag. |
country | optional | ISO code (DE, NL…) for the flag; overrides the guess from the name. |
ovpnConfig | required | A complete OpenVPN client config, inline certificates included. Servers without one are skipped. |
host | optional | The server's address, used for ping before connecting if the config has several remotes. |
username / password | optional | For configs that use auth-user-pass. The app sends them to the server when connecting. |
For safety, DejaVPN never runs scripts from a config: options that would run programs or read files on
the user's device (such as up, down, script-security, plugin) are removed or ignored.
Response headers
| Header | Example | Shown as |
|---|---|---|
profile-title | MyVPN Premium or base64:… | The subscription's name. |
subscription-userinfo | upload=0; download=1073741824; total=53687091200; expire=1795956650 | Data used (upload + download) of the limit (total, bytes; 0 = unlimited), and the end date (expire, Unix seconds; 0 = never). |
subscription-status | active | active, on_hold, limited, expired, device_limit or disabled. |
x-hwid-limit | 3 | Devices allowed (0 = unlimited). |
x-hwid-devices | 1 | Devices registered so far. |
support-url | https://t.me/your_support | A Contact Support item in the subscription's menu (https or mailto only). |
announce | New servers in Turkey this week. | One line of news under the subscription's name. |
profile-update-interval | 6 | How often (hours, 1–24) the app refreshes the subscription in the background. |
The user details can also be put in the body, PasarGuard-style, under "user":
data_limit, used_traffic, expire, status,
hwid_limit, hwid_count. Headers win when both are present.
4. Device limits
- On each request, look up
x-hwidfor the user's token. - Known device, or still under the limit: register it and answer
200. - Over the limit: answer
403withsubscription-status: device_limitand thex-hwid-limit/x-hwid-devicesheaders. DejaVPN tells the user to remove a device in your bot or panel, and keeps the subscription so it works once a slot is free.
5. Check your integration
curl -i https://your-domain.com/open/<token> \ -H "x-hwid: test-device-1" -H "x-device-os: Android" \ -H "x-device-model: Test" -H "User-Agent: DejaVPN/2.1.4 (Android 15)"
You should see 200, your headers, and a JSON body with a servers list where each
server has an ovpnConfig. Then add the link in DejaVPN and connect.
6. Verified partners
Questions
Integration help: [email protected].