Share secrets securely with self-destructing links
⚠️ This link will expire in and can only be viewed once.
Links expire after viewing or set time
No server-side logs of secret content
No registration required
/api
GET /api/health
Returns system status and configuration
💻 Bash Example:
curl https://shh.example.com/api/health
POST /api/secret
💻 Bash Example (Encrypt & Create):
#!/bin/bash
# 1. Generate encryption key (32 bytes = 256 bits)
KEY=$(openssl rand -base64 32 | tr -d '=')
# 2. Encrypt your secret
SECRET="My secret message"
IV=$(openssl rand -base64 12 | tr -d '=')
ENCRYPTED=$(echo -n "$SECRET" | \
openssl enc -aes-256-gcm -K $(echo -n "$KEY" | base64 -d | xxd -p) \
-iv $(echo -n "$IV" | base64 -d | xxd -p) | base64 | tr -d '=')
# 3. Combine IV + encrypted (base64url format)
PAYLOAD="${IV}${ENCRYPTED}"
# 4. Create secret
RESULT=$(curl -s -X POST https://shh.example.com/api/secret \
-H "Content-Type: application/json" \
-d "{\"encryptedData\":\"$PAYLOAD\",\"ttl\":12}")
# 5. Extract secret ID
ID=$(echo $RESULT | jq -r '.id')
# 6. Build shareable URL
echo "Share this URL: https://shh.example.com/view/$ID#$KEY"
⚠️ Note: Client-side encryption via browser is recommended for security
GET /api/secret/{id}/metadata
💻 Bash Example:
curl https://shh.example.com/api/secret/abc-123-def-456/metadata
POST /api/secret/{id}/view
💻 Bash Example (View & Decrypt):
#!/bin/bash # Extract ID and key from URL URL="https://shh.example.com/view/abc-123#xY9zK2..." ID=$(echo $URL | cut -d'/' -f5 | cut -d'#' -f1) KEY=$(echo $URL | cut -d'#' -f2) # Fetch encrypted secret (one-time use!) RESPONSE=$(curl -s -X POST https://shh.example.com/api/secret/$ID/view) ENCRYPTED=$(echo $RESPONSE | jq -r '.encryptedData') # Decrypt (extract IV and ciphertext) IV=$(echo -n "$ENCRYPTED" | head -c 16 | base64 -d) CIPHER=$(echo -n "$ENCRYPTED" | tail -c +17 | base64 -d) # Decrypt with AES-256-GCM echo -n "$CIPHER" | openssl enc -d -aes-256-gcm \ -K $(echo -n "$KEY" | base64 -d | xxd -p) \ -iv $(echo -n "$IV" | xxd -p)
⚠️ Secret is deleted after viewing - can only be retrieved once
File uploads are currently disabled. To enable, set FILE_UPLOADS_ENABLED: true in worker code.
💻 Example when enabled:
{
"encryptedData": "base64url_encrypted_text",
"ttl": 12,
"files": [
{
"name": "document.pdf",
"type": "application/pdf",
"size": 1024000,
"data": "base64url_encrypted_file_content"
}
]
}
Max file size: 10MB. Files are also encrypted client-side.
GET /api/admin/stats
⚠️ Requires admin authentication header
💻 Bash Example:
curl -H "X-Admin-Token: your_admin_token_here" \ https://shh.example.com/api/admin/stats
POST /api/admin/purge
⚠️ Requires admin authentication header
💻 Bash Example:
curl -X POST -H "X-Admin-Token: your_admin_token_here" \ https://shh.example.com/api/admin/purge
DELETE /api/admin/secret/{id}
⚠️ Requires admin authentication header
💻 Bash Example:
curl -X DELETE -H "X-Admin-Token: your_admin_token_here" \ https://shh.example.com/api/admin/secret/abc-123-def-456
Configurable via environment variables
How to decrypt a secret programmatically:
// Extract ID and key from URL
const url = "https://shh.example.com/view/{id}#{key}";
const [path, fragment] = url.split('#');
const secretId = path.split('/').pop();
const encryptionKey = fragment;
// Fetch encrypted secret
const res = await fetch(`/api/secret/${secretId}/view`, {
method: 'POST'
});
const { encryptedData } = await res.json();
// Import key from base64url
const keyBytes = Uint8Array.from(
atob(encryptionKey.replace(/-/g,'+').replace(/_/g,'/')),
c => c.charCodeAt(0)
);
const key = await crypto.subtle.importKey(
'raw', keyBytes, {name: 'AES-GCM'}, false, ['decrypt']
);
// Decrypt
const combined = Uint8Array.from(
atob(encryptedData.replace(/-/g,'+').replace(/_/g,'/')),
c => c.charCodeAt(0)
);
const iv = combined.slice(0, 12);
const ciphertext = combined.slice(12);
const plaintext = await crypto.subtle.decrypt(
{name: 'AES-GCM', iv}, key, ciphertext
);
const secret = new TextDecoder().decode(plaintext);
console.log('Secret:', secret);