Glozr docs

API reference

Signed CTA context

Signed CTA context lets you forward visitor data to your CTA destinations through cryptographically signed URL parameters. When a CTA is configured with forward context, three query parameters are appended to the destination URL — and your backend can verify they really came from Glozr.

URL structure

The outbound URL follows this pattern:

{base-url}?pitchbar_ctx=<base64url-json>
         &pitchbar_ts=<unix-seconds>
         &pitchbar_sig=<hex-hmac-sha256>

Each parameter serves a specific purpose: pitchbar_ctx carries the visitor payload, pitchbar_ts enables replay protection, and pitchbar_sig proves the URL was minted by Glozr.

Whitelisted fields

You can selectively forward any of the following:

  • conversation_id — the active conversation.
  • agent_id — the owning agent.
  • page_url — the visitor's current page.
  • visitor_email — the latest captured email.
  • visitor_name — the latest captured name.
  • captured_fields — custom lead fields, with sensitive keys automatically stripped.

Security measures

  • HMAC-SHA256 signature using a workspace-scoped secret known only to Glozr and your server.
  • Automatic filtering of sensitive field names — any field name matching password, token, secret or api_key is stripped before signing.
  • Five-minute replay window — URLs older than 300 seconds are rejected on verification.
  • Constant-time comparison — both sides use timing-safe equality to resist signature-recovery attacks.

Note. The workspace secret must stay server-side. Never expose it in browser code, mobile apps or any client environment a user can inspect.

Verification

PHP:

function verify_glozr_ctx(array $query, string $secret): ?array {
    $ctx = $query['pitchbar_ctx'] ?? null;
    $ts  = (int)($query['pitchbar_ts']  ?? 0);
    $sig = $query['pitchbar_sig'] ?? '';

    if (!$ctx || !$ts || !$sig) return null;
    if (abs(time() - $ts) > 300) return null;

    $expected = hash_hmac('sha256', "{$ts}.{$ctx}", $secret);
    if (!hash_equals($expected, $sig)) return null;

    return json_decode(base64_decode(strtr($ctx, '-_', '+/')), true);
}

Node.js:

const crypto = require('crypto');

function verify(query, secret) {
  const { pitchbar_ctx: ctx, pitchbar_ts: ts, pitchbar_sig: sig } = query;
  if (!ctx || !ts || !sig) return null;
  if (Math.abs(Date.now()/1000 - Number(ts)) > 300) return null;

  const expected = crypto.createHmac('sha256', secret)
    .update(`${ts}.${ctx}`).digest('hex');
  if (!crypto.timingSafeEqual(Buffer.from(expected,'hex'), Buffer.from(sig,'hex'))) return null;

  return JSON.parse(Buffer.from(ctx, 'base64url').toString());
}

Python:

import base64, hmac, hashlib, json, time

def verify(query, secret):
    ctx = query.get('pitchbar_ctx')
    ts  = int(query.get('pitchbar_ts') or 0)
    sig = query.get('pitchbar_sig', '')
    if not (ctx and ts and sig):
        return None
    if abs(time.time() - ts) > 300:
        return None

    expected = hmac.new(secret.encode(),
                        f"{ts}.{ctx}".encode(),
                        hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        return None

    padded = ctx + '=' * (-len(ctx) % 4)
    return json.loads(base64.urlsafe_b64decode(padded))