How signing works
Each delivery includes anX-Hopae-Signature header with two comma-separated fields:
The signed payload is constructed as:
<raw-request-body> is the exact UTF-8 bytes of the JSON body received in the HTTP request.
Verifying the signature
End-to-end Express example
Useexpress.raw() middleware on the webhook route so the handler receives the untouched body. Express’s default JSON parser destroys the exact bytes the signature was computed over.
Event types
Subscribe to any of these event types via theevents array on PATCH /apps/{clientId}/webhook-config. Each delivery includes the event type in the event field of the payload.
Each payload has the shape:
event is a string; the nested data.event is an object with its own type, timestamp, and optional metadata — they carry the same event type value, but data.event.metadata lets you pass extra context through the delivery pipeline.
Obtaining your webhook secret
Webhook secrets are provisioned server-side when you call POST /apps/{client_id}/webhook-config/rotate-secret. Store the secret securely (e.g. as an environment variable). The secret is also available viaGET /apps/{clientId}/webhook-config.
Replay protection
Thet timestamp in the signature header reflects the moment the delivery was sent. Hopae recommends rejecting webhooks where |now - t| > 300 seconds (5 minutes) to prevent replay attacks, as shown in the examples above.
Backwards compatibility
Apps that have never calledrotate-secret do not have a signing secret configured. For those apps, Hopae delivers webhooks without an X-Hopae-Signature header. Call rotate-secret once to enable signing on your app.
