Features Solutions Technology Tokenomics Docs About
Docs / API Reference / Webhooks

Webhooks

Get notified instantly when products are registered, verified, or transferred.

Setting Up Webhooks

Configure webhooks from your TAG IT dashboard to receive real-time notifications for product events:

  1. Navigate to Settings > Webhooks in your dashboard
  2. Click "Add Webhook Endpoint"
  3. Enter your endpoint URL (must be HTTPS)
  4. Select the events you want to subscribe to
  5. Copy your webhook signing secret for signature verification
  6. Click "Save" to activate the webhook
Note

Your webhook endpoint must respond with a 2xx status code within 30 seconds to be considered successful. Use asynchronous processing for long-running tasks.

Webhook Events

TAG IT sends webhook notifications for the following events:

product.registered

Triggered when a new product is registered on the TAG IT Network:

{
  "event": "product.registered",
  "timestamp": "2024-03-15T14:30:00Z",
  "data": {
    "productId": "prod_abc123",
    "tagId": "NFC-TAG-001-ABC",
    "name": "Luxury Watch Model X",
    "brand": "Premium Timepieces",
    "serialNumber": "SN-2024-001234",
    "owner": "0x742d35Cc6634C0532925a3b844Bc9e7595f5c9E8",
    "txHash": "0xabc123def456..."
  }
}

product.verified

Triggered when a product is scanned and verified:

{
  "event": "product.verified",
  "timestamp": "2024-03-15T15:45:00Z",
  "data": {
    "productId": "prod_abc123",
    "tagId": "NFC-TAG-001-ABC",
    "verified": true,
    "verifiedBy": "0x8901234567890abcdef1234567890abcdef12345",
    "location": {
      "latitude": 40.7128,
      "longitude": -74.0060,
      "city": "New York",
      "country": "US"
    },
    "deviceInfo": {
      "type": "mobile",
      "os": "iOS 17.0"
    }
  }
}

transfer.initiated

Triggered when an ownership transfer is initiated:

{
  "event": "transfer.initiated",
  "timestamp": "2024-03-15T16:00:00Z",
  "data": {
    "transferId": "txfr_xyz789",
    "productId": "prod_abc123",
    "from": "0x742d35Cc6634C0532925a3b844Bc9e7595f5c9E8",
    "to": "0x8901234567890abcdef1234567890abcdef12345",
    "status": "pending",
    "expiresAt": "2024-03-22T16:00:00Z"
  }
}

transfer.completed

Triggered when an ownership transfer is completed:

{
  "event": "transfer.completed",
  "timestamp": "2024-03-15T16:30:00Z",
  "data": {
    "transferId": "txfr_xyz789",
    "productId": "prod_abc123",
    "from": "0x742d35Cc6634C0532925a3b844Bc9e7595f5c9E8",
    "to": "0x8901234567890abcdef1234567890abcdef12345",
    "status": "completed",
    "txHash": "0xdef789ghi012...",
    "completedAt": "2024-03-15T16:30:00Z"
  }
}

Webhook Payload Structure

All webhook payloads follow a consistent structure:

{
  "id": "evt_unique_event_id",
  "event": "event.type",
  "timestamp": "2024-03-15T14:30:00Z",
  "apiVersion": "2024-01-01",
  "data": {
    // Event-specific data
  }
}

Each webhook request includes the following headers:

Content-Type: application/json
X-TagIt-Signature: sha256=abc123...
X-TagIt-Event: product.registered
X-TagIt-Delivery-Id: evt_unique_event_id
X-TagIt-Timestamp: 1710510600

Verifying Webhook Signatures

Always verify webhook signatures to ensure requests are from TAG IT. We use HMAC-SHA256 to sign all webhook payloads.

Security Warning

Never skip signature verification in production. Always validate that webhooks originate from TAG IT before processing.

Here's how to verify webhook signatures in Node.js:

const crypto = require('crypto');

/**
 * Verify TAG IT webhook signature
 * @param {string} payload - Raw request body as string
 * @param {string} signature - X-TagIt-Signature header value
 * @param {string} secret - Your webhook signing secret
 * @param {number} timestamp - X-TagIt-Timestamp header value
 * @returns {boolean} - True if signature is valid
 */
function verifyWebhookSignature(payload, signature, secret, timestamp) {
  // Prevent replay attacks - reject if timestamp is older than 5 minutes
  const currentTime = Math.floor(Date.now() / 1000);
  if (currentTime - timestamp > 300) {
    console.error('Webhook timestamp too old');
    return false;
  }

  // Construct the signed payload
  const signedPayload = `${timestamp}.${payload}`;

  // Compute expected signature
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // Extract signature from header (format: "sha256=...")
  const receivedSignature = signature.replace('sha256=', '');

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(receivedSignature)
  );
}

// Express.js middleware example
const express = require('express');
const app = express();

// Use raw body for signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));

app.post('/webhooks/tagit', (req, res) => {
  const signature = req.headers['x-tagit-signature'];
  const timestamp = parseInt(req.headers['x-tagit-timestamp'], 10);
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET, timestamp)) {
    console.error('Invalid webhook signature');
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Parse and process the webhook
  const event = JSON.parse(payload);

  console.log('Received valid webhook:', event.event);

  // Handle different event types
  switch (event.event) {
    case 'product.registered':
      handleProductRegistered(event.data);
      break;
    case 'product.verified':
      handleProductVerified(event.data);
      break;
    case 'transfer.initiated':
      handleTransferInitiated(event.data);
      break;
    case 'transfer.completed':
      handleTransferCompleted(event.data);
      break;
    default:
      console.log('Unhandled event type:', event.event);
  }

  // Respond quickly with 200 OK
  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Retry Policy

TAG IT uses exponential backoff to retry failed webhook deliveries:

A webhook delivery is considered failed if:

Note

After 6 failed attempts over approximately 34 hours, the webhook is marked as permanently failed. You can view failed deliveries and manually retry them from your dashboard.

Best Practices

Follow these best practices to ensure reliable webhook processing:

Respond Quickly

Return a 200 response immediately and process webhooks asynchronously. If processing takes longer than 30 seconds, the delivery will be marked as failed and retried.

// Good: Respond immediately, process async
app.post('/webhooks/tagit', async (req, res) => {
  // Verify signature first
  if (!verifySignature(req)) {
    return res.status(401).send('Invalid signature');
  }

  // Respond immediately
  res.status(200).json({ received: true });

  // Process asynchronously (e.g., add to queue)
  await queue.add('process-webhook', req.body);
});

Implement Idempotency

Webhooks may be delivered more than once. Use the event ID to ensure you only process each event once:

async function processWebhook(event) {
  // Check if already processed
  const exists = await db.webhookEvents.findById(event.id);
  if (exists) {
    console.log('Event already processed:', event.id);
    return;
  }

  // Store event ID before processing
  await db.webhookEvents.create({
    id: event.id,
    processedAt: new Date()
  });

  // Process the event
  await handleEvent(event);
}

Log Everything

Maintain detailed logs for debugging and auditing:

app.post('/webhooks/tagit', (req, res) => {
  const deliveryId = req.headers['x-tagit-delivery-id'];
  const eventType = req.headers['x-tagit-event'];

  console.log({
    message: 'Webhook received',
    deliveryId,
    eventType,
    timestamp: new Date().toISOString(),
    headers: req.headers,
    bodyPreview: JSON.stringify(req.body).substring(0, 200)
  });

  // ... process webhook
});

Monitor and Alert

Testing Webhooks

Use the Test Webhook button in your dashboard to send test events to your endpoint. You can also use tools like ngrok to test webhooks locally during development.

Edit this page on GitHub
Type to search documentation...