Skip to main content

Server-side events & signing

When you send events to the Bryn custom ingest webhook, you sign each request with a short HMAC signature so Bryn can confirm the request really came from you. This page shows you how.

Get your signing secret​

Your signing secret lives in the Bryn dashboard, under Integrations → Ingest → Custom Webhook. Reveal it there, copy it, and store it somewhere your server can read it (an environment variable or a secrets manager), and treat it like a password. The same page shows the endpoint URL to send events to.

note

The signing secret is only ever used on your server. Never put it in a browser, a mobile app, or anything a visitor can view. If it's ever exposed, regenerate it from the same Custom Webhook page; the old value stops working once the changeover completes.

The signature header​

Every signed request carries an X-Bryn-Signature header:

X-Bryn-Signature: t=<unix-seconds>,v1=<hex-digest>
  • t is the current time in whole seconds since the Unix epoch.
  • v1 is a hex-encoded HMAC-SHA256 digest of the string <t>.<body> (the timestamp, a literal dot, then the exact request body), keyed with your signing secret.

Sign a request​

  1. 1
    Build the request body

    Serialize your event once, and keep that exact string. You sign and send the same bytes: re-serializing the JSON afterwards can reorder keys or change whitespace and break the signature.

  2. 2
    Compute the signature

    Take the current Unix time as t, join it to the body as <t>.<body>, and compute the HMAC-SHA256 digest of that string with your signing secret.

  3. 3
    Send the request

    POST the body to your tenant's ingest URL with the X-Bryn-Signature header. Send within five minutes of the timestamp you signed; Bryn rejects signatures outside that window.

SECRET="whsec_your_signing_secret" # Bryn dashboard → Integrations → Ingest → Custom Webhook
TENANT="your-tenant-id"
BODY='{"eventType":"demo.requested","properties":{"email":"alex@acme.com"}}'

TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -X POST "https://bryn.civic.com/ingest/custom/$TENANT" \
-H "Content-Type: application/json" \
-H "X-Bryn-Signature: t=$TS,v1=$SIG" \
--data-raw "$BODY"

Linking to a known company or person​

By default Bryn resolves each event to a company (and to a person when you send an email) from the fields in the body. If you already know which Bryn record an event belongs to, name it directly with a bryn block in properties:

{
"eventType": "demo.requested",
"properties": {
"bryn": { "entityId": "39dbb553-5940-41fd-ab43-479c9bceac37" },
"email": "alex@acme.com"
}
}
  • entityId attaches the event to that company.
  • individualId attaches it to a specific person.

You get these ids from the Bryn pixel: when it recognizes a visitor, it exposes a stable entity.entityId and individual.individualId in its personalization payload. Capture them on your site and echo them back on your server-side events so both sources stitch to the same records. See Personalization Recipes for how the pixel surfaces visitor data.

What Bryn checks​

A request is accepted only when all of these hold:

  • The X-Bryn-Signature header is present and well-formed.
  • The timestamp is within five minutes of Bryn's clock.
  • The v1 digest matches Bryn's own HMAC of <t>.<body> using your signing secret.

If any check fails, Bryn responds with 401 and the event is not recorded. A successful request returns 204.

Rotating your secret​

Regenerate the signing secret from the same Custom Webhook page whenever you need to. Update your server to sign with the new value; the previous secret keeps working briefly during the changeover so in-flight requests aren't dropped.