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:
- Navigate to Settings > Webhooks in your dashboard
- Click "Add Webhook Endpoint"
- Enter your endpoint URL (must be HTTPS)
- Select the events you want to subscribe to
- Copy your webhook signing secret for signature verification
- Click "Save" to activate the webhook
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.
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:
- Retry 1: 1 minute after initial failure
- Retry 2: 5 minutes after retry 1
- Retry 3: 30 minutes after retry 2
- Retry 4: 2 hours after retry 3
- Retry 5: 8 hours after retry 4
- Retry 6: 24 hours after retry 5
A webhook delivery is considered failed if:
- Your endpoint returns a non-2xx status code
- The connection times out (30 seconds)
- The endpoint is unreachable
- SSL/TLS verification fails
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
- Set up monitoring for webhook processing success rates
- Alert on repeated failures or signature mismatches
- Track processing time to identify bottlenecks
- Use your dashboard to view delivery history and troubleshoot issues
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.