Reference · Trust and verification
Verifying replies
Ed25519, message labels, and why a MAC is not enough.
How your backend tells our answer from one the visitor wrote.
Why the signature is asymmetric#
The verdict passes through the visitor's browser on its way to your backend. They can edit it.
A MAC is not enough, and the reason is simple: only whoever can forge a MAC can check it. If your backend holds the verification key, that key can also sign, and a key leaked at a customer is a key that mints verdicts.
Ed25519 solves that: we hold the private half, you verify with the public one.
The first line is a label#
The signed message begins with a label naming the kind of message:
| Label | Belongs to |
|---|---|
hideguard-collect-ed25519-v2 | /api/v1/collect replies |
hideguard-verdict-ed25519-v2 | /api/v1/verdict-public replies |
hideguard-behavior-ack-ed25519-v2 | /api/v1/behavior acknowledgements |
The label is domain separation, and the site depends on it. A verdict-public reply has the exact same field list as a collect reply. Under one shared label, a visitor holding collect replies of their own could offer an older, cleaner one where this endpoint's current answer was expected.
What is signed#
A canonical field list, not the body bytes. The page may re-serialise the reply, so signing bytes would break immediately.
The list is: environment, requestId, reasonCodes (in the order served, so it cannot be shuffled), score, siteId, verdict, visitorId.
A field added to the JSON is not thereby inside the signature. If you rely on a field, check it is in the list above.
siteId and environment are in it, which is why both appear in the reply, the signing key is per workspace.
No key means no signature field#
With no signing key, or a field outside the signature charset, the reply carries no signature field at all: never one that looks signed.
Time tolerance#
The signature carries a timestamp. The default accepts replies within 300 seconds and allows 30 seconds of clock skew.