Webhook Integration
Webhooks let your server receive real-time events instead of polling. Recommended for production.
Events
| Event | When fired |
|---|---|
payload.signed | User signed (and optionally submitted) the transaction |
payload.rejected | User explicitly rejected in their wallet |
payload.expired | Payload TTL elapsed without resolution |
Payload body
Every webhook POST has this shape:
{
"event": "payload.signed",
"payload": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"type": "signAndSubmit",
"status": "signed",
"signerAddress": "rXXX...",
"txHash": "ABC123...",
"txBlob": "1200002200000000...",
"walletAdapter": "crossmark",
"createdAt": "2026-05-08T12:00:00.000Z",
"resolvedAt": "2026-05-08T12:01:23.000Z"
}
}Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-XRPL-Request-Signature | sha256=<hmac> |
X-XRPL-Request-Event | payload.signed | payload.rejected | payload.expired |
X-XRPL-Request-Delivery | Delivery attempt ID |
HMAC signatures are a Pro plan feature. On the Free tier, the signature header is omitted.
Setting up a webhook
Register the webhook
const webhook = await client.webhooks.create({
url: 'https://yourapp.com/xrplrequest-webhook',
events: ['payload.signed', 'payload.rejected', 'payload.expired'],
});
// ⚠ Save webhook.secret — it's shown once
process.env.WEBHOOK_SECRET = webhook.secret;Handle incoming requests
import express from 'express';
import { verifyWebhookSignature } from '@xrplrequest/sdk';
const app = express();
// ⚠ Must use raw body — JSON middleware will break signature verification
app.post('/xrplrequest-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-xrpl-request-signature'] as string;
const rawBody = req.body.toString('utf-8');
if (!verifyWebhookSignature(process.env.WEBHOOK_SECRET!, rawBody, sig)) {
return res.status(401).send('Invalid signature');
}
const { event, payload } = JSON.parse(rawBody);
switch (event) {
case 'payload.signed':
console.log('Signed:', payload.txHash);
break;
case 'payload.rejected':
console.log('Rejected by user');
break;
case 'payload.expired':
console.log('Request expired');
break;
}
res.sendStatus(200); // respond quickly — process async if needed
});Retries
If your endpoint returns anything other than 2xx, XRPL Request will retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | immediate |
| 2 | 2s |
| 3 | 4s |
| 4 | 8s |
| 5 | 16s |
After 5 failed attempts the delivery is marked failed. You can see delivery history in the dashboard.
Best practices
- Always verify the signature before processing (Pro plan)
- Respond with
200immediately and process the event asynchronously to avoid timeouts - Use idempotency — a
uuiduniquely identifies a payload; your handler may be called more than once if a retry occurs - Deduplicate by
X-XRPL-Request-Deliveryif you need strict once-only processing