Dynamic Links
Create redirect URLs on-the-fly, without storing any record in Hozip. The destination is signed into the URL itself.
How it works
A dynamic link is a short URL whose destination is not stored in Hozip's database.
Instead, the destination URL is encrypted into a signature query parameter.
When a visitor opens the link, Hozip decrypts the signature, applies your group's settings
(monetization, access rules, analytics), and redirects the visitor — all without any
prior API call from your side.
Dynamic link URL shape:
https://DOMAIN/e/GROUP_ID?signature=TOKEN
Because no database record is written, you can generate millions of unique destination URLs instantly, entirely on your own infrastructure.
Prerequisites
- A group ID — open Dashboard → Groups, pick or create a group, and copy its ID.
- The group key — on the same Group Settings page, copy the Key field next to the ID. This is a base64url-encoded 32-byte key unique to your group.
-
A domain — any domain listed under Enabled Domains works. Use the domain hostname (e.g.
hozip.link).
Signature format
The signature token is produced in three steps:
1. Build the JSON payload
{
"destination_url": "https://example.com/landing?ref=campaign",
"back_param": "utm_source=hozip&utm_medium=dynamic"
}
| Field | Type | Required | Description |
|---|---|---|---|
destination_url
|
string | Required |
The URL the visitor will be redirected to. Must start with
https:// (plain http:// is automatically
upgraded). Max 2048 characters.
|
back_param
|
string | No |
Additional query-string parameters to append to
destination_url at redirect time.
Provide without a leading ? or & —
Hozip adds the right separator automatically.
Example: "ref=hozip&click_id=ABC123".
|
2. Encrypt the payload
Serialize the payload as compact JSON (no extra whitespace), then encrypt it using
AES-256-GCM with an empty AAD (b""):
key = base64url_decode(GROUP_KEY) # 32 bytes
iv = random_bytes(12) # 12-byte nonce (GCM standard)
plaintext = json.dumps(payload).encode() # UTF-8
aad = b"" # empty additional authenticated data
ciphertext_and_tag = AES_256_GCM_encrypt(key, iv, plaintext, aad)
# (the 16-byte GCM authentication tag is appended to the ciphertext)
3. Encode the token
Concatenate and base64url-encode without padding:
raw = b"\x01" + iv + ciphertext_and_tag
token = base64url_no_padding_encode(raw)
| Bytes | Length | Description |
|---|---|---|
0x01
|
1 byte |
Version marker — always 0x01.
|
| IV | 12 bytes | Random nonce. Must be unique per token. |
| Ciphertext + tag | variable + 16 bytes | AES-GCM ciphertext followed by the 16-byte authentication tag. |
Base64url encoding uses the URL-safe alphabet (- and _
instead of + and /) and omits = padding.
Building the final URL
https://DOMAIN/e/GROUP_ID?signature=TOKEN
URL-encode the TOKEN when inserting it into the query string.
Example:
https://hozip.link/e/018e1b2c-0000-7f8a-9b0c-1d2e3f4a5b6c?signature=AQoK...
Code examples
Python
import base64
import json
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from urllib.parse import quote
GROUP_KEY_B64U = "YOUR_GROUP_KEY" # from Dashboard → Group Settings → Key
GROUP_ID = "YOUR_GROUP_ID" # from Dashboard → Group Settings → ID
DOMAIN = "hozip.link" # any enabled domain
def b64u_decode(s: str) -> bytes:
pad = "=" * (-len(s) % 4)
return base64.urlsafe_b64decode(s + pad)
def b64u_encode(b: bytes) -> str:
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def build_dynamic_link(destination_url: str, back_param: str = "") -> str:
key = b64u_decode(GROUP_KEY_B64U)
payload = {"destination_url": destination_url}
if back_param:
payload["back_param"] = back_param
iv = os.urandom(12)
aead = AESGCM(key)
plaintext = json.dumps(payload, separators=(",", ":")).encode()
ct_and_tag = aead.encrypt(iv, plaintext, b"") # empty AAD
token = b64u_encode(b"\x01" + iv + ct_and_tag)
return f"https://{DOMAIN}/e/{GROUP_ID}?signature={quote(token)}"
# --- usage ---
url = build_dynamic_link(
destination_url="https://example.com/landing",
back_param="utm_source=hozip&utm_medium=dynamic",
)
print(url)
Node.js
import crypto from "crypto";
const GROUP_KEY_B64U = "YOUR_GROUP_KEY"; // Dashboard → Group Settings → Key
const GROUP_ID = "YOUR_GROUP_ID"; // Dashboard → Group Settings → ID
const DOMAIN = "hozip.link";
function b64uDecode(s) {
const pad = "=".repeat((4 - (s.length % 4)) % 4);
return Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/") + pad, "base64");
}
function b64uEncode(buf) {
return buf.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}
function buildDynamicLink(destinationUrl, backParam = "") {
const key = b64uDecode(GROUP_KEY_B64U);
const payload = { destination_url: destinationUrl };
if (backParam) payload.back_param = backParam;
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
cipher.setAAD(Buffer.alloc(0)); // empty AAD
const plaintext = Buffer.from(JSON.stringify(payload));
const ct = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag(); // 16 bytes
const raw = Buffer.concat([Buffer.from([0x01]), iv, ct, tag]);
const token = b64uEncode(raw);
return `https://${DOMAIN}/e/${GROUP_ID}?signature=${encodeURIComponent(token)}`;
}
// --- usage ---
const url = buildDynamicLink(
"https://example.com/landing",
"utm_source=hozip&utm_medium=dynamic"
);
console.log(url);
PHP
<?php
function b64uDecode(string $s): string {
$pad = str_repeat("=", (4 - strlen($s) % 4) % 4);
return base64_decode(strtr($s, "-_", "+/") . $pad);
}
function b64uEncode(string $bin): string {
return rtrim(strtr(base64_encode($bin), "+/", "-_"), "=");
}
function buildDynamicLink(
string $groupKeyB64u,
string $groupId,
string $domain,
string $destinationUrl,
string $backParam = ""
): string {
$key = b64uDecode($groupKeyB64u);
$iv = random_bytes(12);
$payload = ["destination_url" => $destinationUrl];
if ($backParam !== "") $payload["back_param"] = $backParam;
$plaintext = json_encode($payload, JSON_UNESCAPED_SLASHES);
$tag = "";
$ct = openssl_encrypt(
$plaintext, "aes-256-gcm", $key,
OPENSSL_RAW_DATA, $iv, $tag, "", 16
);
$raw = "\x01" . $iv . $ct . $tag;
$token = b64uEncode($raw);
return "https://{$domain}/e/{$groupId}?signature=" . urlencode($token);
}
// --- usage ---
echo buildDynamicLink(
groupKeyB64u: "YOUR_GROUP_KEY",
groupId: "YOUR_GROUP_ID",
domain: "hozip.link",
destinationUrl: "https://example.com/landing",
backParam: "utm_source=hozip&utm_medium=dynamic"
);
Behaviour at redirect time
When a visitor opens a dynamic link, Hozip:
-
Decrypts and verifies the
signatureusing the group key. -
Validates
destination_url(must behttps://, max 2048 chars, not on the blocked domain list). - Applies your group's access rules (geographic filters, bot detection, etc.). Blocked visitors are redirected to a safe fallback.
- Serves the configured monetization flow (if any) for the group.
-
Appends
back_param(if provided) todestination_urland performs the final redirect.
If the signature is invalid or the destination URL fails validation, the visitor is silently redirected to the domain's default fallback URL — no error page is shown.
Limitations
| Constraint | Value |
|---|---|
| Destination URL scheme |
https:// required (http:// is auto-upgraded)
|
| Destination URL max length |
2048 characters (after back_param is appended)
|
| Token version |
0x01 — only this version is accepted
|
| Key rotation | If you delete and recreate a group, its key changes and old tokens become invalid |
| API rate limit | None — tokens are generated locally; no request is made to Hozip's API |