Saltar al contenido principal

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

EventDescription
budget.createdA new budget has been created
budget.updatedAn existing budget has been modified
budget.approvedA budget has been approved
budget.rejectedA budget has been rejected
budget.expiredA budget has expired
budget.deletedA budget has been deleted

Customer Events

EventDescription
customer.createdA new customer has been created
customer.updatedAn existing customer has been modified
customer.deletedA customer has been deleted

Product Events

EventDescription
product.createdA new product has been created
product.updatedAn existing product has been modified
product.deletedA 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

FieldTypeDescription
eventstringThe event type that triggered the webhook
timestampstringISO 8601 timestamp when the event occurred
dataobjectThe resource data related to the event
metadataobjectAdditional 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

  1. Extract the signature from the X-Billoget-Signature header
  2. Compute the expected signature using your webhook secret
  3. 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

  1. Navigate to API Settings in your Billoget dashboard
  2. Add a webhook endpoint with your URL
  3. Select events you want to receive
  4. Configure security settings (IP whitelisting, signature verification)
  5. Test the webhook using the test endpoint

Webhook Settings

SettingDescription
URLThe endpoint where webhooks will be delivered
EventsWhich events to send to this webhook
SecretSecret key for HMAC signature verification
IP WhitelistRestrict webhook delivery to specific IPs
Retry PolicyConfigure 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

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
612 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


Ready to start receiving real-time notifications? Set up your first webhook in the Billoget dashboard! 🚀