Set Up Webhooks

Configure webhooks to receive real-time notifications when objects change state.

8 minBeginner

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.

javascript
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'));
Security: Your webhook secret is in your org settings under Developer → Webhook Secret. Never hardcode it — use environment variables.

Step 2 — Register the Webhook

Tell DUAL to send events to your endpoint:

bash
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:

bash
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:

json
{
  "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:

bash
curl https://gateway-48587430648.europe-west6.run.app/webhooks \
  -H "Authorization: Bearer $DUAL_TOKEN"

Delete a webhook by ID:

bash
curl -X DELETE https://gateway-48587430648.europe-west6.run.app/webhooks/{webhookId} \
  -H "Authorization: Bearer $DUAL_TOKEN"
Retry Policy: DUAL retries failed webhook deliveries with exponential backoff (1s, 5s, 30s, 5m). After 5 consecutive failures, the webhook is automatically deactivated. Re-enable it via PATCH /webhooks/{id}.

What's Next?

Ready to add monetization? Head to Integrate Payments.