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,secretorapi_keyis 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))