PlaygentDocs

DevelopersVerify signatures

Verify signatures

Playgent signs two things with HMAC-SHA256: every webhook delivery, so your server knows it came from Playgent, and, on your side, each signed-in reader's id, so Playgent knows your site vouched for the reader. Try both below.

Check it here

Paste a real delivery, or press Try an example. Everything runs in your browser.

Check

Fill in the three fields, or try an example.

Checked in your browser. Nothing you type is sent anywhere.

Webhook deliveries

Each delivery has a Playgent-Signature header:

Text
Playgent-Signature: t=1729775112,v1=5f2b8c0e…
  1. Read the raw body

    Take the body bytes exactly as they arrived, before parsing the JSON. Parsing and printing it again changes the bytes.

  2. Split the header

    Split it on ,. Take t from t=, and every value after v1=.

  3. Compute the signature

    HMAC-SHA256 of <t>.<raw body>, keyed with your Webhook secret (the whole text, including pgw_), as lowercase hex.

  4. Compare

    Accept the delivery if any v1 matches, comparing in constant time. Reject it if t is more than 5 minutes from now.

JavaScript
import { createHmac, timingSafeEqual } from 'node:crypto'

export function verify(rawBody, header, secret) {
  const parts = header.split(',')
  const t = parts.find((part) => part.startsWith('t='))?.slice(2) ?? ''
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
  const mac = createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest()
  return parts.filter((part) => part.startsWith('v1=')).some((part) => {
    const got = Buffer.from(part.slice(3), 'hex')
    return got.length === mac.length && timingSafeEqual(got, mac)
  })
}

Signed-in readers

Your server signs each reader's id so their results arrive with verified: true. The signature is HMAC-SHA256 of the reader's id, keyed with your Signing secret from SettingsEmbedSigning secret (the whole text, including pgs_), as 64 lowercase hex characters.

JavaScript
createHmac('sha256', process.env.PLAYGENT_SIGNING_SECRET).update(String(user.id)).digest('hex')

Pass the result as sig to Playgent.identify(). See signed-in readers.

Two secrets, two jobs

Signing secretWebhook secret
Starts withpgs_pgw_
WhereSettingsEmbedSigning secretSettingsEmbedResults webhook
SignsYour readers' ids, on your serverEach webhook delivery, at Playgent
After a rotationThe old one works for 7 more daysDeliveries carry both signatures for 7 days

Keep both on your server only. Never put them in a page, an app or a public repository.