Encode VPN subscription URLs into incy://crypt1/<payload> deep links
that the INCY iOS, Android, and Desktop clients
decode automatically.
https://sub.your-provider.example/abc123token
⬇
incy://crypt1/AAECAwQFBgcICQoLNyIQL3rDwRZqnyoD8pGK…
Open the resulting link on a device with INCY installed → the subscription imports without the user copy-pasting anything.
npm install @incy/link-encoderimport { encryptLink, decryptLink } from '@incy/link-encoder';
const link = encryptLink('https://sub.your-provider.example/abc123token', {
name: 'My Provider VPN',
});
console.log(link);
// → incy://crypt1/AAECAwQFBgcICQoLNyIQL3rDwRZqnyoD8pGK…
// Decryption mainly for testing — the INCY apps do this end-side.
const decoded = decryptLink(link);
console.log(decoded.url, decoded.name);encryptLink(url, opts?) accepts:
| Field | Type | Notes |
|---|---|---|
url |
string |
The http(s) subscription URL. Required. |
opts.name |
string? |
Display name shown in the receiver's import sheet. |
A small, dependency-free encoder for embedding subscription URLs in chat messages and websites without exposing the raw URL to scanners, moderation bots, or screenshots.
This is not encryption-for-secrecy. The AES-256-GCM key is derived from constants and binary assets shipped inside this package — anyone reading the source can reconstruct it.
The exact same key already lives inside every INCY client (iOS, Android, Desktop). Anyone with a copy of those apps could already extract it using standard mobile reverse-engineering tools. Publishing this package reveals nothing new — it just makes the limitation explicit.
| Defended | |
|---|---|
| Telegram chat moderation bots | ✅ |
| Russian regulator (RKN) automated scanners | ✅ |
| Casual screenshots and clipboard mishaps | ✅ |
grep over chat dumps |
✅ |
| Determined reverse engineer with Frida | ❌ |
If the key is ever published publicly (e.g. extracted and shared on
Twitter), a future INCY release will introduce crypt2/ with a fresh
key. Existing crypt1/ links in chat histories will keep working
forever — the clients never remove old schemes.
encryptLink(url: string, opts?: { name?: string }): string
decryptLink(link: string): { url: string; name?: string }
// For deterministic tests only — never reuse an IV with different
// plaintexts in production code.
encryptLinkDeterministic(url: string, opts: { iv: Buffer; name?: string }): string
// Runtime info
VERSION: string // package version
SCHEME_VERSION: string // current deep-link scheme, e.g. "crypt1"
KEY_FINGERPRINT: string // SHA-256 of K1 — for sanity checksA link generated by this package decodes bit-for-bit identically on
iOS (CryptoKit), Android (javax.crypto), and Desktop (Compose
Multiplatform JVM, also javax.crypto). A test vector pinned in the
test suite guards against drift between updates.
MIT