Webhook Implementation Guide

Webhooks allow your application to receive real-time notifications about events that occur within the Sharmo platform. Instead of continuously polling our API for changes, webhooks push data to your application as events happen.

Introduction

Webhooks are HTTP callbacks that allow Sharmo to notify your application when specific events occur. They offer several advantages:

Setting Up Webhooks

To use webhooks with Sharmo, follow these steps:

1. Create an Endpoint on Your Server

First, you need to create an endpoint on your server that will receive webhook notifications. This endpoint should:

2. Register Your Webhook in the Developer Dashboard

  1. Log in to your Sharmo Developer Account
  2. Navigate to the Application Dashboard
  3. Select the application you want to configure
  4. Go to the "Webhooks" tab
  5. Click "Add Webhook"
  6. Enter your endpoint URL
  7. Select the events you want to subscribe to
  8. Generate a webhook secret
  9. Save your configuration
Webhook Configuration Example
{
  "url": "https://example.com/webhooks/sharmo",
  "events": [
    "property.created",
    "property.updated",
    "token.transferred",
    "transaction.completed"
  ],
  "secret": "whsec_abcdefghijklmnopqrstuvwxyz1234567890",
  "active": true
}

Webhook Events

Sharmo provides webhooks for a variety of events across different resources:

Property Events

Token Events

Transaction Events

User Events

Security

Securing your webhook endpoint is crucial. Sharmo uses signature verification to ensure that webhook events are legitimate:

Webhook Signatures

Each webhook request includes a Sharmo-Signature header containing:

To verify the signature:

JavaScript - Signature Verification
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  // Parse the signature header
  const components = signature.split(',');
  const timestamp = components.find(c => c.startsWith('t=')).substring(2);
  const receivedSignature = components.find(c => c.startsWith('v1=')).substring(3);
  
  // Create the string to sign
  const signedPayload = `${timestamp}.${payload}`;
  
  // Create the expected signature
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  
  // Compare signatures (use constant-time comparison in production)
  return expectedSignature === receivedSignature;
}

// Usage in Express.js
app.post('/webhooks/sharmo', (req, res) => {
  const signature = req.headers['sharmo-signature'];
  const payload = JSON.stringify(req.body);
  const secret = 'whsec_abcdefghijklmnopqrstuvwxyz1234567890';
  
  if (!verifyWebhookSignature(payload, signature, secret)) {
    return res.status(403).send('Invalid signature');
  }
  
  // Process the webhook
  const event = req.body;
  console.log(`Received event: ${event.type}`);
  
  // Respond quickly
  res.status(200).send('Webhook received');
  
  // Process asynchronously
  handleWebhookEvent(event).catch(console.error);
});

Timestamp Tolerance

For additional security, check that the webhook timestamp is recent (typically within 5 minutes) to prevent replay attacks:

PHP - Timestamp Verification
function isTimestampValid($timestamp, $tolerance = 300) {
    $now = time();
    return ($now - $timestamp) < $tolerance;
}

// In your webhook handler
$signatureParts = explode(',', $_SERVER['HTTP_SHARMO_SIGNATURE']);
$timestamp = substr(strstr($signatureParts[0], '='), 1);

if (!isTimestampValid($timestamp)) {
    http_response_code(403);
    echo 'Webhook timestamp expired';
    exit;
}

Implementation Examples

Node.js (Express)

Node.js Webhook Handler
const express = require('express');
const crypto = require('crypto');
const app = express();

// Parse JSON request body
app.use(express.json({
  verify: (req, res, buf) => {
    // Save raw body for signature verification
    req.rawBody = buf.toString();
  }
}));

app.post('/webhooks/sharmo', async (req, res) => {
  const signature = req.headers['sharmo-signature'];
  const webhookSecret = process.env.SHARMO_WEBHOOK_SECRET;
  
  try {
    // Verify signature
    const isValid = verifySignature(req.rawBody, signature, webhookSecret);
    
    if (!isValid) {
      console.error('Invalid webhook signature');
      return res.status(403).send('Invalid signature');
    }
    
    // Extract event data
    const event = req.body;
    
    // Respond immediately to acknowledge receipt
    res.status(200).send('Webhook received');
    
    // Process the event asynchronously
    switch (event.type) {
      case 'property.created':
        await handlePropertyCreated(event.data);
        break;
      case 'token.transferred':
        await handleTokenTransferred(event.data);
        break;
      case 'transaction.completed':
        await handleTransactionCompleted(event.data);
        break;
      default:
        console.log(`Unhandled event type: ${event.type}`);
    }
  } catch (error) {
    console.error('Error processing webhook:', error);
    // Still return 200 to acknowledge receipt
    if (!res.headersSent) {
      res.status(200).send('Webhook received');
    }
  }
});

function verifySignature(payload, signature, secret) {
  if (!signature || !secret) return false;
  
  try {
    const components = signature.split(',');
    const timestamp = components.find(c => c.startsWith('t=')).substring(2);
    const receivedSig = components.find(c => c.startsWith('v1=')).substring(3);
    
    // Check timestamp is recent (within 5 minutes)
    const now = Math.floor(Date.now() / 1000);
    if (now - parseInt(timestamp) > 300) {
      return false;
    }
    
    // Compute expected signature
    const signedPayload = `${timestamp}.${payload}`;
    const expectedSig = crypto
      .createHmac('sha256', secret)
      .update(signedPayload)
      .digest('hex');
      
    return crypto.timingSafeEqual(
      Buffer.from(expectedSig),
      Buffer.from(receivedSig)
    );
  } catch (error) {
    console.error('Error verifying signature:', error);
    return false;
  }
}

// Start the server
app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Processing Event Data

Event Handling Examples
// Example event handlers

async function handlePropertyCreated(data) {
  // Store the new property in your database
  await db.properties.insert({
    propertyId: data.property_id,
    address: data.address,
    price: data.price,
    tokenizationStatus: data.tokenization_status,
    createdAt: new Date(data.created_at)
  });
  
  // Notify relevant users
  await notificationService.notifyUsersAboutNewProperty(data);
  
  console.log(`Processed property.created event for ${data.property_id}`);
}

async function handleTokenTransferred(data) {
  // Update token ownership records
  await db.tokenTransactions.insert({
    transactionId: data.transaction_id,
    tokenId: data.token_id,
    fromAddress: data.from_address,
    toAddress: data.to_address,
    amount: data.amount,
    timestamp: new Date(data.timestamp)
  });
  
  // Update user balances
  await db.tokenBalances.updateMany([
    { userId: data.from_user_id, $inc: { balance: -data.amount } },
    { userId: data.to_user_id, $inc: { balance: data.amount } }
  ]);
  
  console.log(`Processed token.transferred event for ${data.transaction_id}`);
}

async function handleTransactionCompleted(data) {
  // Update transaction status
  await db.transactions.updateOne(
    { transactionId: data.transaction_id },
    { $set: { status: 'completed', completedAt: new Date() } }
  );
  
  // Notify user(s)
  await notificationService.sendTransactionConfirmation(
    data.user_id,
    data.transaction_id,
    data.amount
  );
  
  console.log(`Processed transaction.completed event for ${data.transaction_id}`);
}

Best Practices

1. Acknowledge Webhooks Quickly

Always respond to webhook requests with a 200 OK status code as quickly as possible. If the request processing takes longer than 5 seconds, Sharmo will consider it failed and may retry.

2. Process Events Asynchronously

Handle the webhook data processing in a background job, task queue, or separate thread after you've acknowledged receipt.

3. Handle Retries and Duplicates

Webhooks may be delivered multiple times for the same event. Design your event handlers to be idempotent (safe to execute multiple times for the same event).

Idempotent Event Handler
async function handleIdempotentEvent(eventId, eventData) {
  // Check if we've already processed this event
  const exists = await db.processedEvents.exists(eventId);
  
  if (exists) {
    console.log(`Event ${eventId} already processed, skipping`);
    return;
  }
  
  // Process the event
  await processEvent(eventData);
  
  // Mark as processed
  await db.processedEvents.insert({
    eventId: eventId,
    processedAt: new Date()
  });
}

4. Implement Proper Error Handling

Set up robust error handling and logging for webhook processing to ensure you can diagnose and resolve issues.

5. Test in Sandbox Environment

Use our sandbox environment to test webhook integration before deploying to production. The sandbox allows you to trigger test events manually.

6. Monitor Webhook Delivery

Check the Developer Dashboard for webhook delivery status and failed deliveries. Set up alerts for consistent webhook failures.

Troubleshooting

Common Issues

Issue Possible Causes Solution
Webhook not being received
  • Incorrect endpoint URL
  • Firewall blocking requests
  • Webhook not activated
Verify URL, check firewall settings, confirm webhook is active in dashboard
Signature verification failures
  • Incorrect webhook secret
  • Body parsing issues
  • HTTP proxy modifying requests
Verify secret matches dashboard, ensure raw body is available for verification
Webhook timeouts
  • Processing takes too long
  • Database connection issues
  • Third-party API delays
Move processing to background jobs, optimize database queries, return 200 immediately
Repeated webhook deliveries
  • Non-200 responses
  • Slow response times
Implement idempotent event handling, ensure quick 200 responses

Debugging Tools

If you continue to experience issues with webhooks, contact our support team at developer-support@sharmo.io.