Webhooks are the push half of Moove Payment Links. The pull half —
GET /v1/payment-link — still works, and remains the way to reconcile state you may have missed.Register an endpoint in the console
Endpoints are registered in the Moove Business console at moove.xyz/business/manage/webhooks, under Add Endpoint. There is no API call for this. Two fields:string
required
A public
https:// address with no explicit port — any port in the URL is rejected, even :443. Maximum 2048 characters.It cannot be changed later. To move to a new destination, add a second endpoint and delete the first — both fire until you delete the old one, so the swap has no gap.string
required
Your label for the endpoint, so you can tell
prod from staging. Maximum 100 characters.
The hostname is resolved and every address it returns must be publicly routable. It is checked again at send time — a host that stops resolving publicly stops receiving deliveries.
You can hold up to 5 endpoints at once. Deleted endpoints do not count towards it.
The signing secret is shown once
Adding an endpoint returns its signing secret. It starts withwhsec_, it is displayed exactly once, and it is never shown again — not in the list, not through the API.
Store it before you dismiss the dialog. If you lose it, add a new endpoint and delete the old one; there is no rotation and no way to re-read it.
The console lists a short prefix of the secret afterwards, which identifies the endpoint but cannot be used to sign anything.
Events
Every endpoint receives every event. There is no subscription picker.
A final payment on a single-use link fires both:
payment_link.transaction.succeeded for the money and payment_link.completed for the link. They are delivered separately and can arrive in either order.
Deactivating a link or letting it expire sends no event. Pick those up from GET /v1/payment-link.
Branch on type. Ignore any type you do not recognise rather than erroring — new types can be added.
The request Moove sends
POST to your URL, Content-Type: application/json, with three headers:
Body
string
required
The event id. Matches the
Moove-Event-Id header.string
required
payment_link.transaction.succeeded or payment_link.completed.string
required
ISO 8601 timestamp of when the event occurred — not when this attempt was sent. It is identical on every retry.
object
required
The payment link’s state at send time.
payment_link.transaction.succeeded
Nothing about the payer is included. The payer is a third party, and whatever your own checkout collected is already yours.The payload is a notification, not a replacement for the API. When you need more than it carries, read the link back with its
paymentLinkId.Verify every delivery
Your endpoint is a public URL. Anyone can POST to it, so treat an unsigned or badly signed request as hostile. The signature isv1= followed by the hex HMAC-SHA256 of {Moove-Timestamp}.{raw body}, keyed by your signing secret.
- Read the raw body, before any JSON middleware touches it. Most frameworks need to be told to keep it.
- Compare in constant time. A plain string comparison leaks the digest a byte at a time.
- Reject stale timestamps. The timestamp is inside the signed message, so it cannot be edited independently — a few minutes of tolerance is enough to stop a captured delivery being replayed later.
- De-duplicate on
Moove-Event-Id. Delivery is at-least-once; see below. The id is per endpoint, so two endpoints receiving the same event see different ids.
Reply fast, then do the work
acknowledgement
required
The delivery is done. Nothing else is sent for that event.
3xx. Redirects are not followed — a redirect on a signed POST would replay the body, and its signature, to a host you never registered.
The connection times out after 10 seconds. Verify, enqueue, return 200. Do not settle an order, call a payment processor or send an email before replying.
Retries run for about 15 hours
A failed attempt is retried up to 6 attempts in total:
After the sixth, the delivery is marked failed and abandoned. Nothing is re-sent, and the event is not replayed when your endpoint recovers — reconcile the gap with
GET /v1/payment-link.
Disabling or deleting an endpoint abandons anything still queued for it. A disabled endpoint is a pause, not a buffer — re-enabling it does not replay what it missed.
Inspect what was sent
Each endpoint in the console has View deliveries: the event type, status, attempt count, the HTTP code your server returned, and the error text where there was one.
The endpoint list also shows the last delivery outcome and a consecutive-failure count — enough to answer “is my webhook working” without opening the log.
The response body your server returns is never stored, and never shown back to you.
What is not available
Registering, editing and deleting endpoints are console actions, not API calls — see above.There is no endpoint test button, no manual replay of a failed delivery, and no secret rotation. To change a secret or a URL, add a new endpoint and delete the old one.
Next
Moove Payment Links
The endpoints these events are about.
Errors
Branching on
code.Moove Agentic Payments
The wider integration.