Outbound webhooks
Outbound webhooks notify your server about selected application events. Configure the webhook URL and secret through the Vudy dashboard for the API key or app used to create transactions. This documentation does not cover programmatic key or webhook provisioning APIs.
Verified delivery behavior#
- Delivery is a single
POSTof JSON{ "event": "<name>", "data": … } - Authentication header:
X-Webhook-Secret(shared secret from dashboard configuration; omitted when no secret is configured) - Failures are logged; automatic retries are not provided
- No request idempotency keys and no streaming
Make your handler idempotent and treat webhooks as a convenience. Always be able to poll transaction status as the source of truth.
Schema events#
Dashboard webhook subscriptions may reference these event names (the only values in the app webhook schema):
| Event | Meaning | Delivery status |
|---|---|---|
requestPaid | Vudy payment request confirmed paid | Delivered on payment confirmation |
requestCancelled | Payment request cancelled | Reserved — not currently delivered |
requestClientEmailSet | Client email set on a payment request | Reserved — not currently delivered |
deploymentPaid | Deployment payment completed | Reserved — not currently delivered |
deploymentIssue | Deployment encountered an issue | Reserved — not currently delivered |
There is no sendCompleted event. Poll transaction status for Vudy sends and for any workflow that only has reserved events above.
Example receiver#
import http from "node:http";
const SECRET = process.env.VUDY_WEBHOOK_SECRET;
http
.createServer(async (req, res) => {
if (req.method !== "POST") {
res.writeHead(405).end();
return;
}
if (req.headers["x-webhook-secret"] !== SECRET) {
res.writeHead(401).end("unauthorized");
return;
}
const chunks = [];
for await (const c of req) chunks.push(c);
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
const { event, data } = body;
if (event === "requestPaid") {
// Upsert by data.id (the Vudy tx id).
await markRequestPaid(data);
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end('{"ok":true}');
})
.listen(8080);Example payload shape#
{
"event": "requestPaid",
"data": {
"id": "TX_UUID",
"requestId": "REQUEST_UUID",
"token": "USDC",
"amount": 100.5,
"amountCurrencySymbol": "USD",
"tokenAmount": "100.5",
"hashUrl": "https://etherscan.io/tx/0x...",
"network": "Ethereum"
}
}Use data.id as the transaction id and data.requestId as the payment request id. Confirm final state with GET /channel/vudy/request/{requestId} or GET /v1/tx/{id}.
curl simulation (local testing)#
curl -sS https://your.app/webhooks/vudy \
-H "Content-Type: application/json" \
-H "X-Webhook-Secret: YOUR_DASHBOARD_SECRET" \
-d '{"event":"requestPaid","data":{"id":"TX_UUID","requestId":"REQUEST_UUID"}}'Out of scope#
- Programmatic API-key provisioning that attaches webhook URLs
- Inbound provider webhooks (platform ingress)
- Guarantees of automatic retries, ordering, or exactly-once delivery