Set Up Webhooks
Configure webhooks to receive real-time notifications when objects change state.
Prerequisites
- A DUAL account
- A publicly accessible HTTPS endpoint
What You'll Build
Webhooks let your server receive real-time HTTP callbacks when events occur in DUAL — like object transfers, property changes, or new mints. In this tutorial you'll register a webhook endpoint, configure event filters, and handle incoming payloads.
Step 1 — Create Your Endpoint
Set up an HTTPS endpoint on your server that accepts POST requests. Every webhook handler should verify the HMAC-SHA256 signature before processing — this ensures the request genuinely came from DUAL.
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.DUAL_WEBHOOK_SECRET;
function verifySignature(body, timestamp, signature) {
const message = `${timestamp}.${body}`;
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(message)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
app.post('/webhooks/dual', (req, res) => {
const sig = req.headers['x-dual-signature'];
const ts = req.headers['x-dual-timestamp'];
if (!sig || !ts || !verifySignature(JSON.stringify(req.body), ts, sig)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = req.body;
console.log('Event:', event.event_type);
console.log('Object:', event.object_id);
console.log('Data:', event.payload);
res.status(200).json({ received: true });
});
app.listen(3000, () => console.log('Webhook server on :3000'));Step 2 — Register the Webhook
Tell DUAL to send events to your endpoint:
curl -X POST https://gateway-48587430648.europe-west6.run.app/webhooks \
-H "Authorization: Bearer $DUAL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhook": {
"url": "https://your-server.com/webhooks/dual",
"events": ["object.transferred", "object.updated"],
"active": true
}
}'Step 3 — Test with a Transfer
Transfer an object to trigger a webhook delivery:
curl -X POST https://gateway-48587430648.europe-west6.run.app/ebus/execute \
-H "Authorization: Bearer $DUAL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": {
"transfer": {
"id": "your-object-id",
"to": "recipient-dual-wallet-id"
}
}
}'Within seconds your endpoint should receive a POST with a payload like:
{
"event_type": "object.transferred",
"object_id": "abc-123",
"timestamp": "2026-03-13T10:30:00Z",
"payload": {
"previous_owner": "wallet-a",
"new_owner": "wallet-b"
}
}Step 4 — Manage Your Webhooks
List all registered webhooks:
curl https://gateway-48587430648.europe-west6.run.app/webhooks \
-H "Authorization: Bearer $DUAL_TOKEN"Delete a webhook by ID:
curl -X DELETE https://gateway-48587430648.europe-west6.run.app/webhooks/{webhookId} \
-H "Authorization: Bearer $DUAL_TOKEN"PATCH /webhooks/{id}.
What's Next?
Ready to add monetization? Head to Integrate Payments.