Webhooks
Receive real-time notifications when events occur in your Venshack account.
Overview
Webhooks allow your application to receive automated HTTP POST requests when certain events happen. Instead of polling the API to check for updates, webhooks push data to your server in real-time.
Setting Up Webhooks
1. Configure Your Endpoint
Create an endpoint on your server that can receive POST requests. The endpoint must:
- Accept POST requests with JSON body
- Return a 2xx status code within 30 seconds
- Be accessible over HTTPS (required for production)
2. Register Your Webhook
Register your webhook endpoint via the API or dashboard:
POST /v1/webhooks
Content-Type: application/json
Authorization: Bearer your_api_key
{
"url": "https://your-app.com/webhooks/venshack",
"events": ["vend.completed", "vend.failed", "payment.received"],
"secret": "whsec_your_webhook_secret"
}
Response
{
"success": true,
"data": {
"id": "whk_abc123",
"url": "https://your-app.com/webhooks/venshack",
"events": ["vend.completed", "vend.failed", "payment.received"],
"status": "ACTIVE",
"createdAt": "2024-01-15T10:30:00Z"
}
}
Webhook Events
Subscribe to the events relevant to your integration:
Vending Events
| Event | Description |
|---|---|
vend.initiated |
A vend request has been submitted and is being processed |
vend.completed |
Electricity token has been successfully generated |
vend.failed |
Vend request failed (with error details) |
vend.refunded |
Failed vend has been refunded to the wallet |
Payment Events
| Event | Description |
|---|---|
payment.received |
Payment has been received and credited to wallet |
payment.failed |
Payment attempt failed |
wallet.credited |
Wallet balance has been credited |
wallet.debited |
Wallet balance has been debited |
Access Code Events
| Event | Description |
|---|---|
access_code.used |
An access code has been used for gate entry |
access_code.expired |
An access code has expired |
Webhook Payload
All webhook payloads follow this structure:
{
"id": "evt_xyz789",
"type": "vend.completed",
"timestamp": "2024-01-15T14:30:00Z",
"data": {
"vendId": "vnd_abc123",
"meterNumber": "12345678901",
"amount": 5000,
"units": 45.5,
"token": "1234-5678-9012-3456-7890"
}
}
Payload Fields
| Field | Type | Description |
|---|---|---|
id |
string | Unique event identifier for idempotency |
type |
string | The event type (e.g., vend.completed) |
timestamp |
string | ISO 8601 timestamp when the event occurred |
data |
object | Event-specific data payload |
Verifying Webhooks
Always verify webhook signatures to ensure requests are from Venshack and haven't been tampered with.
Signature Header
Each webhook includes a X-Venshack-Signature header containing
an HMAC-SHA256 signature.
Verification Example (Node.js)
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post('/webhooks/venshack', (req, res) => {
const signature = req.headers['x-venshack-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process the webhook
const { type, data } = req.body;
switch (type) {
case 'vend.completed':
// Handle successful vend
await notifyResident(data.token);
break;
case 'vend.failed':
// Handle failed vend
await alertAdmin(data.error);
break;
}
res.status(200).json({ received: true });
});
Always use constant-time comparison (like
crypto.timingSafeEqual) when comparing signatures to prevent
timing attacks.
Handling Webhooks
Respond Quickly
Your endpoint must respond within 30 seconds. If processing takes longer, acknowledge the webhook immediately and process asynchronously:
app.post('/webhooks/venshack', async (req, res) => {
// Verify signature first
if (!verifyWebhook(req)) {
return res.status(401).end();
}
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
await processWebhookAsync(req.body);
});
Handle Retries
If your endpoint returns a non-2xx response or times out, Venshack will retry with exponential backoff:
- 1st retry: 1 minute after initial attempt
- 2nd retry: 5 minutes
- 3rd retry: 30 minutes
- 4th retry: 2 hours
- 5th retry: 8 hours
Use the
id field to ensure you don't process the same event
twice. Store processed event IDs and check before processing.
Idempotency Example
const processedEvents = new Set(); // Use Redis in production
app.post('/webhooks/venshack', async (req, res) => {
const { id, type, data } = req.body;
// Check if already processed
if (processedEvents.has(id)) {
return res.status(200).json({ received: true });
}
// Mark as processed
processedEvents.add(id);
// Process the event
await handleEvent(type, data);
res.status(200).json({ received: true });
});
Testing Webhooks
Sandbox Mode
In sandbox mode, you can trigger test webhook events from the dashboard to verify your integration.
Local Development
Use a tool like ngrok to expose your local server for testing:
# Install ngrok and start tunnel
ngrok http 3000
# Use the HTTPS URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/venshack
Managing Webhooks
List Webhooks
GET /v1/webhooks
Authorization: Bearer your_api_key
Update Webhook
PATCH /v1/webhooks/whk_abc123
Content-Type: application/json
Authorization: Bearer your_api_key
{
"events": ["vend.completed", "vend.failed"],
"status": "ACTIVE"
}
Delete Webhook
DELETE /v1/webhooks/whk_abc123
Authorization: Bearer your_api_key