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:
- Real-time updates: Receive notifications as soon as events occur
- Reduced API calls: Eliminate the need to poll our API for updates
- Event-driven architecture: Build more reactive applications
- Improved user experience: Provide immediate feedback to your users
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:
- Accept POST requests
- Process JSON payloads
- Return a 200 OK response quickly (within 5 seconds)
- Handle any complex processing asynchronously
2. Register Your Webhook in the Developer Dashboard
- Log in to your Sharmo Developer Account
- Navigate to the Application Dashboard
- Select the application you want to configure
- Go to the "Webhooks" tab
- Click "Add Webhook"
- Enter your endpoint URL
- Select the events you want to subscribe to
- Generate a webhook secret
- Save your configuration
{
"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
property.created
- A new property is added to the platformproperty.updated
- Property details are updatedproperty.tokenized
- A property is tokenizedproperty.sold
- A property is soldproperty.delisted
- A property is removed from listings
Token Events
token.issued
- New tokens are created for a propertytoken.transferred
- Tokens are transferred between userstoken.price_updated
- The market price of tokens changestoken.dividend_issued
- Dividend payments are issued to token holders
Transaction Events
transaction.created
- A new transaction is initiatedtransaction.pending
- A transaction is waiting for confirmationtransaction.completed
- A transaction is successfully completedtransaction.failed
- A transaction fails to completetransaction.refunded
- A transaction is refunded
User Events
user.registered
- A new user registers on the platformuser.kyc_updated
- A user's KYC status changesuser.kyc_verified
- A user passes KYC verificationuser.kyc_rejected
- A user fails KYC verification
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:
t
- Timestamp when the signature was createdv1
- Signature in the formatHMAC-SHA256(timestamp + '.' + payload)
To verify the signature:
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:
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)
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
// 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).
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 |
|
Verify URL, check firewall settings, confirm webhook is active in dashboard |
Signature verification failures |
|
Verify secret matches dashboard, ensure raw body is available for verification |
Webhook timeouts |
|
Move processing to background jobs, optimize database queries, return 200 immediately |
Repeated webhook deliveries |
|
Implement idempotent event handling, ensure quick 200 responses |
Debugging Tools
- Webhook Logs: Check delivery logs in the Developer Dashboard
- Event Viewer: Inspect event details and replay events
- Local Testing: Use tools like ngrok or localtunnel to test with local development environments
If you continue to experience issues with webhooks, contact our support team at developer-support@sharmo.io.