The header
Every request carries a single signature header:Signing secret
Each webhook has its own secret in the formatwhsec_<base64>. You receive it in the response to registering the webhook, in the signingSecret field.
If you lose the secret, you can read it again from the GET /api/pay/plugin/webhook/{webhookId} response - there is no need to rotate it for that reason.
Algorithm
- Read the
X-Paymove-Signatureheader. - Split the value on the comma into the
t=<timestamp>andv1=<signature>parts. - Build the signed content:
{timestamp}.{raw_request_body}. - Compute HMAC-SHA256 using the full secret (with prefix) as the key.
- Base64-encode the result and prepend
v1=. - Compare it with the signature from the header using a timing-safe comparison.
Code
Node.js
Python
Java
Test vector
Check your implementation against the values below - skip the tolerance window, since the timestamp is in the past:
If your function returns a different signature, check in order: that you use the full secret including the
whsec_ prefix, that you encode to base64 (not hex), and that you sign the raw body without re-serializing it.
Replay protection
The timestamp is covered by the signature, so it cannot be tampered with - but Paymove itself does not reject old requests. Your server decides how long a signature stays valid. The recommended window is 5 minutes; the examples above already apply it. On top of that, fulfil orders idempotently, keyed onexternalId. A redelivered notification must never result in shipping the goods twice.
Rotating the secret
What’s next
Payment statuses
What the webhook payload contains and how to check a payment’s state.
Webhook configuration
Registering a webhook and assigning it to a product.