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.