Webhooks API
Webhooks allow you to receive real-time notifications when events occur in your Billoget account. This enables you to build reactive integrations that respond immediately to changes in budgets, customers, and other resources.
Overview
Billoget webhooks are HTTP callbacks that are triggered when specific events occur. When an event happens, we'll send a POST request to your configured webhook URL with details about the event.
Key Features
- Real-time notifications for budget and customer events
- Secure delivery with HMAC signature verification
- Automatic retries with exponential backoff
- Event filtering to receive only relevant notifications
- Delivery tracking and analytics
Webhook Events
Budget Events
Event | Description |
---|---|
budget.created | A new budget has been created |
budget.updated | An existing budget has been modified |
budget.approved | A budget has been approved |
budget.rejected | A budget has been rejected |
budget.expired | A budget has expired |
budget.deleted | A budget has been deleted |
Customer Events
Event | Description |
---|---|
customer.created | A new customer has been created |
customer.updated | An existing customer has been modified |
customer.deleted | A customer has been deleted |
Product Events
Event | Description |
---|---|
product.created | A new product has been created |
product.updated | An existing product has been modified |
product.deleted | A product has been deleted |
Webhook Payload Structure
All webhook payloads follow a consistent structure:
{
"event": "budget.created",
"timestamp": "2024-01-16T10:30:00Z",
"data": {
"id": "budget_123",
"customerId": "customer_456",
"total": 1500.0,
"status": "PENDING",
"createdAt": "2024-01-16T10:30:00Z",
"customer": {
"id": "customer_456",
"name": "Acme Corp",
"email": "contact@acme.com"
}
},
"metadata": {
"source": "api",
"version": "1.4.0",
"delivery_id": "delivery_789"
}
}
Payload Fields
Field | Type | Description |
---|---|---|
event | string | The event type that triggered the webhook |
timestamp | string | ISO 8601 timestamp when the event occurred |
data | object | The resource data related to the event |
metadata | object | Additional metadata about the event |
Security
HMAC Signature Verification
All webhook requests include an HMAC signature in the X-Billoget-Signature
header. This allows you to verify that the request came from Billoget and hasn't been tampered with.
Signature Format
X-Billoget-Signature: sha256=<signature>
Verification Process
- Extract the signature from the
X-Billoget-Signature
header - Compute the expected signature using your webhook secret
- Compare the signatures using a secure comparison method
Example Verification (Node.js)
const crypto = require("crypto");
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
const actualSignature = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, "hex"),
Buffer.from(actualSignature, "hex")
);
}
// Usage
const isValid = verifyWebhookSignature(
req.body,
req.headers["x-billoget-signature"],
process.env.WEBHOOK_SECRET
);
Example Verification (Python)
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
actual_signature = signature.replace('sha256=', '')
return hmac.compare_digest(expected_signature, actual_signature)
# Usage
is_valid = verify_webhook_signature(
request.body,
request.headers.get('X-Billoget-Signature'),
os.environ['WEBHOOK_SECRET']
)
Test Webhook Endpoint
Use this endpoint to test your webhook configuration and verify that your endpoint is working correctly.
Request
POST /api/public/webhooks/test
Request Body
{
"event": "budget.created",
"data": {
"id": "budget_123",
"customerId": "customer_456",
"total": 1500.0,
"status": "PENDING",
"createdAt": "2024-01-15T10:30:00Z"
}
}
Example Request
curl -X POST "https://api.billoget.com/v1/api/public/webhooks/test" \
-H "Authorization: Bearer bk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"event": "budget.created",
"data": {
"id": "budget_123",
"customerId": "customer_456",
"total": 1500.00,
"status": "PENDING",
"createdAt": "2024-01-15T10:30:00Z"
}
}'
Response
{
"data": {
"delivery_id": "delivery_789",
"status": "delivered",
"response_code": 200,
"response_time": 150,
"delivered_at": "2024-01-16T10:30:00Z"
},
"message": "Webhook test delivered successfully"
}
Webhook Configuration
Setting Up Webhooks
- Navigate to API Settings in your Billoget dashboard
- Add a webhook endpoint with your URL
- Select events you want to receive
- Configure security settings (IP whitelisting, signature verification)
- Test the webhook using the test endpoint
Webhook Settings
Setting | Description |
---|---|
URL | The endpoint where webhooks will be delivered |
Events | Which events to send to this webhook |
Secret | Secret key for HMAC signature verification |
IP Whitelist | Restrict webhook delivery to specific IPs |
Retry Policy | Configure retry behavior for failed deliveries |
Best Practices
1. Idempotency
Handle duplicate webhook deliveries gracefully:
const processedEvents = new Set();
app.post("/webhook", (req, res) => {
const deliveryId = req.body.metadata.delivery_id;
if (processedEvents.has(deliveryId)) {
return res.status(200).send("Already processed");
}
// Process the event
processEvent(req.body);
processedEvents.add(deliveryId);
res.status(200).send("OK");
});
2. Async Processing
Process webhooks asynchronously to avoid timeouts:
app.post("/webhook", async (req, res) => {
// Acknowledge receipt immediately
res.status(200).send("OK");
// Process asynchronously
setImmediate(() => {
processWebhookEvent(req.body);
});
});
3. Error Handling
Implement proper error handling and logging:
app.post("/webhook", (req, res) => {
try {
// Verify signature
if (!verifySignature(req.body, req.headers["x-billoget-signature"])) {
return res.status(401).send("Invalid signature");
}
// Process event
processEvent(req.body);
res.status(200).send("OK");
} catch (error) {
console.error("Webhook processing error:", error);
res.status(500).send("Internal server error");
}
});
4. Event Filtering
Filter events at the application level if needed:
app.post("/webhook", (req, res) => {
const { event, data } = req.body;
// Only process budget approval events
if (event === "budget.approved") {
processBudgetApproval(data);
}
res.status(200).send("OK");
});
Delivery and Retries
Delivery Expectations
- Timeout: 30 seconds
- Expected Response: HTTP 2xx status code
- Retry Policy: Exponential backoff with jitter
- Max Retries: 5 attempts over 24 hours
Retry Schedule
Attempt | Delay |
---|---|
1 | Immediate |
2 | 1 minute |
3 | 5 minutes |
4 | 30 minutes |
5 | 2 hours |
6 | 12 hours |
Monitoring Deliveries
Track webhook delivery status in your Billoget dashboard:
- Delivery success rate
- Response times
- Failed deliveries
- Retry attempts
Troubleshooting
Common Issues
1. Webhook Not Receiving Events
Check:
- Webhook URL is accessible from the internet
- Firewall allows incoming requests
- SSL certificate is valid (for HTTPS URLs)
- Webhook is enabled in dashboard
2. Signature Verification Failing
Check:
- Using the correct webhook secret
- Signature computation matches our algorithm
- Request body hasn't been modified
- Using timing-safe comparison
3. Timeouts
Check:
- Webhook endpoint responds within 30 seconds
- Processing is done asynchronously
- Database queries are optimized
- External API calls are non-blocking
Debug Mode
Enable debug mode to see detailed webhook logs:
app.post("/webhook", (req, res) => {
console.log("Webhook received:", {
event: req.body.event,
timestamp: req.body.timestamp,
signature: req.headers["x-billoget-signature"],
body: JSON.stringify(req.body),
});
res.status(200).send("OK");
});
Example Implementations
Express.js Webhook Handler
const express = require("express");
const crypto = require("crypto");
const app = express();
app.use(express.raw({ type: "application/json" }));
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
function verifySignature(payload, signature) {
const expectedSignature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(payload, "utf8")
.digest("hex");
const actualSignature = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, "hex"),
Buffer.from(actualSignature, "hex")
);
}
app.post("/webhook", (req, res) => {
const signature = req.headers["x-billoget-signature"];
if (!verifySignature(req.body, signature)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
switch (event.event) {
case "budget.created":
handleBudgetCreated(event.data);
break;
case "budget.approved":
handleBudgetApproved(event.data);
break;
default:
console.log("Unhandled event:", event.event);
}
res.status(200).send("OK");
});
function handleBudgetCreated(budget) {
console.log("New budget created:", budget.id);
// Add your business logic here
}
function handleBudgetApproved(budget) {
console.log("Budget approved:", budget.id);
// Add your business logic here
}
app.listen(3000, () => {
console.log("Webhook server running on port 3000");
});
Next.js API Route
// pages/api/webhook.js
import crypto from "crypto";
export default function handler(req, res) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const signature = req.headers["x-billoget-signature"];
const payload = JSON.stringify(req.body);
if (!verifySignature(payload, signature)) {
return res.status(401).json({ error: "Invalid signature" });
}
const { event, data } = req.body;
// Process the event
console.log("Received event:", event, data);
res.status(200).json({ received: true });
}
function verifySignature(payload, signature) {
const secret = process.env.WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
const actualSignature = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, "hex"),
Buffer.from(actualSignature, "hex")
);
}
Next Steps
- Test Webhooks - Use Postman to test webhook delivery
- Authentication - Learn about API key management
- Rate Limiting - Understand webhook rate limits
- Error Handling - Handle webhook errors gracefully
Ready to start receiving real-time notifications? Set up your first webhook in the Billoget dashboard! 🚀