Verify before parsing
Your public HTTPS endpoint receives the three headers below:Skinloop-Timestamp is the current Unix timestamp in seconds, encoded as a
decimal string. Compare the lowercase hexadecimal digest with
Skinloop-Signature: v1=... using a constant-time comparison:
Skinloop-Event-Id to equal the body id. See the
complete webhook requests for all
headers and body fields.
Complete Node and Express receiver
Install Express and your PostgreSQL client, then register this route before any globalexpress.json() middleware. The route uses express.raw() because
parsing and reserializing JSON changes the signed bytes.
View the complete Node and Express receiver
View the complete Node and Express receiver
recordEventAndEnqueue represents your local transactional status update and
durable fulfillment-job insert from Safe fulfillment.
Implement it with your own order schema. It must use the supplied database
client so event storage and job creation commit together.Timestamp policy
The receiver must reject a timestamp more than five minutes in the past or future relative to its own UTC clock. This limits replay exposure while allowing ordinary network delay and clock skew.- Synchronize production hosts with a reliable time service.
- Perform the timestamp check before HMAC work and JSON parsing.
- Do not use
createdAtfor signature freshness; useSkinloop-Timestamp. - Do not treat the freshness check as deduplication. An attacker can replay a valid request within five minutes, so the event-ID unique constraint remains required.
Endpoint secrets
Each webhook endpoint has its own signing secret beginning withwhsec_.
Skinloop displays the full secret only when the endpoint is created or its
secret is rotated. Store it as a server-side secret:
Rotate without dropping events
The direct Rotate secret action replaces an endpoint’s secret immediately and returns the new value once. Because the new value cannot be deployed before it is created, use two endpoint records for a planned zero-downtime rotation:- Create a second webhook endpoint with the same HTTPS URL and event subscriptions. Securely capture its new secret.
- Deploy the new secret as
CURRENTand the old endpoint’s secret asPREVIOUS. - Trigger a webhook test for the new endpoint and confirm it verifies.
- Revoke the old endpoint in Skinloop.
- Keep the old secret as
PREVIOUSfor one hour to cover requests already in flight. - Remove
PREVIOUSand test the remaining endpoint again.
CURRENT as quickly as possible, and remove the compromised
secret rather than keeping an overlap. A short rejection window is safer than
accepting a known-compromised secret.
The receiver checks both configured secrets during planned overlap. Do not keep
old secrets indefinitely.
Response contract
Return 2xx only after durable storage and any required fulfillment job commit.
Do not return 2xx first and continue important work only in process memory.
Next: test retries and accepted duplicates in
Webhook retries and deduplication.
