Verify a delivery

What a reporting receiver must do before it trusts a body — what to do while Cockpit does not show the signing secret, the HMAC scheme for when it does, the timestamp header, returning 2xx fast, and when the platform retries.

A delivery arrives over the public internet at a URL anyone could discover. Verify first, then read the body. An unverified body must not reach your store, or your reporting numbers are whatever a stranger decided to POST.

The scheme, code samples in three languages, replay handling and secret rotation are documented in Security. This page is the reporting-receiver checklist — what a pipeline in particular has to get right.

You can't verify a signature yet. Every delivery is signed, but Cockpit does not show the signing secret (as of 2026-09). Until it does, no body should reach your figures on its own word: dedupe on the event id, then re-read the entity through the API and store what the API returns. Step 2 below applies once you have the secret.

The four steps

  1. Capture the raw body bytes before any JSON parsing.
  2. Recompute the HMAC-SHA256 of those bytes with your endpoint's signing secret (the whole whsec_… string), hex-encode it, and compare against the hex after v0= in X-Revenexx-Signature. Compare in constant time. During a secret rotation the header carries two comma-separated signatures, newest first — accept the delivery if any of them matches.
  3. Dedupe on the X-Revenexx-Event-Id header. The body is the event's data and does not repeat the id.
  4. Return 2xx fast, then do the work out of band.

Once you can verify, reject an unverifiable request with 401. Do not process it, and do not return 2xx — an unverifiable request is not a real delivery.

Do not re-serialize the body to verify it. Parsing the JSON and re-encoding changes whitespace, key order and number formatting, so the bytes differ and the HMAC will not match. This is the single most common cause of "verification is broken" — and in a framework that parses bodies for you by default, you have to go out of your way to keep the raw bytes.

The timestamp header

Every delivery carries X-Revenexx-Timestamp, an RFC 3339 instant. You may reject a delivery whose timestamp is implausibly far from your own clock, but it is not covered by the signature — Security → Replay, and what the timestamp is worth explains what that check does and does not buy you.

For a reporting receiver the point is narrower: dedupe on X-Revenexx-Event-Id is the control that protects your figures. A timestamp window does not, because the same event counted twice is a wrong number whether or not it arrived on time.

What to return, and when it retries

Your responseThe platform
Any 2xxStops. You have taken responsibility for the event.
A status of 400 or aboveTreats it as failed and retries, 11 attempts over about 8.5 hours.
A connection error, or no answer within 5 secondsSame — failed, retried.

Acknowledge first, work later. Verify, record the raw event, return 2xx — then transform, aggregate and load. A receiver that does its ETL before responding is a receiver that trips the timeout on the exact day the batch is largest, and then gets the same event again while it is still busy with the first copy.

When the attempts run out, the event is not sent again, which for a reporting pipeline is indistinguishable from "the metrics went flat". See Delivery.

The other headers

HeaderUse it for
X-Revenexx-SignatureThe HMAC to verify.
X-Revenexx-Event-IdDeduplication — the envelope's id, which the body does not carry.
X-Revenexx-TopicRouting, before you parse the body.
X-Revenexx-TimestampThe tolerance check above (RFC 3339).

Routing on the header lets a receiver hand a body to the right consumer without parsing it first, which keeps the acknowledge path short.

Where to go next

Was this page helpful?