Spectatr.ai

Webhooks

Encryption

Clip data is encrypted with a secret held only by you and us. The cipher is authenticated, so a payload that did not originate with us, or that was altered in transit, fails to decrypt rather than yielding wrong data.


Parameters

ParameterValueNotes
AlgorithmAES-256-GCMAn authenticated cipher. AES-CBC is not a substitute, because it provides no integrity and must not be used.
Key256-bitThe shared secret, delivered as 64 hex characters
Nonce96-bitSent as the nonce field, base64-encoded
Auth tag128-bitAppended to the ciphertext, so payload decodes to ciphertext followed by tag
Additional datanonePass an empty value or null
PlaintextUTF-8 JSONThe object specified above

Reference implementation

Python
# pip install cryptography
import base64, json
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def decrypt_delivery(secret_hex: str, nonce_b64: str, payload_b64: str) -> dict:
    key   = bytes.fromhex(secret_hex)          # 32 bytes
    nonce = base64.b64decode(nonce_b64)        # 12 bytes
    blob  = base64.b64decode(payload_b64)      # ciphertext || 16-byte tag
    plaintext = AESGCM(key).decrypt(nonce, blob, None)
    return json.loads(plaintext)
Node.js
// no dependencies
const crypto = require('crypto');

function decryptDelivery(secretHex, nonceB64, payloadB64) {
  const key   = Buffer.from(secretHex, 'hex');       // 32 bytes
  const nonce = Buffer.from(nonceB64, 'base64');     // 12 bytes
  const blob  = Buffer.from(payloadB64, 'base64');

  const tag        = blob.subarray(blob.length - 16);
  const ciphertext = blob.subarray(0, blob.length - 16);

  const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
  decipher.setAuthTag(tag);

  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
  return JSON.parse(plaintext.toString('utf8'));
}