Webhooks & events
Receive signed events on your server, verify them, and respond. Failed deliveries retry automatically.
Event
payment confirmed
Sign
HMAC-SHA256
POST
to your URL
Retry
on failure
2xx
acknowledged
Event types
payment.completedeventoptional | A payment for one of your products was confirmed on-chain. |
payment.receivedeventoptional | Any incoming payment to a monitored wallet (wallet-monitoring). |
Payloads
payment.completed
json
{ "event": "payment.completed", "productId": "prod_123", "paymentId": "pay_123", "amount": "25", "asset": "XLM", "wallet": "GABC...XYZ", "transactionHash": "f6afcc6b...", "status": "confirmed", "timestamp": "2026-01-01T00:00:00.000Z"}payment.received
json
{ "id": "evt_123", "type": "payment.received", "createdAt": "2026-01-01T00:00:00.000Z", "data": { "wallet": { "id": "wal_123", "address": "GABC...XYZ" }, "amount": "25", "asset": "XLM", "from": "G...", "to": "GABC...XYZ", "transactionHash": "f6afcc6b..." }}Delivery headers
x-stellarhooks-eventstringoptional | The event type. |
x-stellarhooks-deliverystringoptional | Unique delivery id. |
x-stellarhooks-timestampstringoptional | Unix ms timestamp used in the signature. |
x-stellarhooks-signaturestringoptional | HMAC signature, format sha256=…. |
Verifying signatures
Compute sha256 = HMAC_SHA256(secret, timestamp + "." + rawBody) and compare it to the x-stellarhooks-signature header using a constant-time comparison. Use the raw request body — re-serializing the JSON will change the bytes and break verification.
import crypto from "node:crypto"; // rawBody must be the exact bytes of the request body (not re-serialized JSON).export function verifySignature(headers, rawBody, secret) { const signature = headers["x-stellarhooks-signature"]; const timestamp = headers["x-stellarhooks-timestamp"]; const expected = "sha256=" + crypto .createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));}Responding & retries
- Return a
2xxstatus quickly to acknowledge. Any non-2xx (or timeout) is treated as a failure. - Failed deliveries retry on a backoff schedule:
30s → 2m → 10m → 1h, then the delivery is marked exhausted. - Every attempt is logged and can be replayed from the dashboard.
- Deliveries can repeat — make your handler idempotent on
transactionHash/paymentId.
Build and test against a temporary endpoint (e.g. webhook.site) before pointing at production.