Webhooks
Webhooks allow you to build integrations that subscribe to events in your Xemelgo account. When an event occurs—like a new cycle count being created or inventory being updated—Xemelgo sends an HTTP POST request to your configured endpoint with event details.
How Webhooks Work
- Register a webhook with your endpoint URL, signing secret, and event topics you want to subscribe to
- Xemelgo sends events to your endpoint as they happen in real-time
- Your endpoint receives the POST request, verifies the signature, and processes the event
- Respond with 200 OK to acknowledge receipt
This event-driven approach eliminates the need to poll the API for changes, reducing latency and server load.
When to Use Webhooks
Webhooks are ideal for:
- Real-time notifications - Trigger workflows immediately when inventory changes
- Audit logging - Track all events in your own database
- Integration automation - Sync data with external systems
- User notifications - Alert users about important events
Managing Webhooks
Use the provided GraphQL APIs to manage your webhook subscriptions:
Registering Webhooks
To set up webhooks for your account, call the registerWebhook AppSync API with the topics you want to subscribe to, the endpoint where events should be sent, and a secret key.
The secret key must be a string greater than 24 characters and will be used to verify the authenticity of incoming webhook requests.
Example:
mutation MyMutation {
registerWebhook(
input: {
topics: ["inventory.cycle_count"]
secret: "your-secret-key-must-be-longer-than-24-characters"
endpoint: "https://ab1d316d1bbe.ngrok-free.app"
}
) {
webhook {
id
topics
endpoint
creationDate
}
}
}
Listing Webhooks
Use the webhooks query to view all your active webhook subscriptions:
query {
webhooks {
webhooks {
id
endpoint
topics
creationDate
}
}
}
Unregistering Webhooks
A webhook can be deleted by using the unregisterWebhook API with the webhook subscription ID:
mutation {
unregisterWebhook(input: { id: "webhook-12345" }) {
webhook {
id
topics
endpoint
creationDate
}
}
}
Managing Multiple Topics
Each webhook subscription can listen to multiple event topics. Update your subscriptions by unregistering and re-registering with new configurations.
Receiving Webhooks
Webhooks notify your application when events happen in your Xemelgo account. Xemelgo uses HTTPS to send webhook events to your app as JSON payloads that include relevant event data.
Endpoint Requirements
Create an endpoint on your server that can:
- Accept POST requests with a JSON payload
- Return a 2xx status code quickly (within 5 seconds)
- Be available over HTTPS (required for production)
Request Format
Headers
| Header | Description |
|---|---|
Content-Type | Always application/json |
xemelgo-signature | HMAC signature for verifying the webhook (format: sha256=<hex>) |
Body
{
"id": "c1314cc5-60f7-ddba-7be3-900030a8ee05",
"eventTimestamp": 1765572297000,
"topic": "inventory.cycle_count",
"data": {
// Event-specific data
}
}
The topic field indicates the event type, while data contains the relevant event data.
Verify Webhook Signatures
Xemelgo signs webhook requests so you can verify they originated from Xemelgo and not a third party. We strongly recommend verifying signatures before processing webhook events.
How It Works
Xemelgo generates signatures using HMAC with SHA-256. Each webhook request includes an xemelgo-signature header containing the signature.
To verify:
- Extract the signature from the header (removing the
sha256=prefix) - Compute the HMAC of the raw request body using your signing secret
- Compare both signatures using a constant-time comparison function
Example Verification in JavaScript
const crypto = require("crypto");
function verifyXemelgoSignature(signature, body, signingSecret) {
if (!signature?.startsWith("sha256=")) {
throw new Error("Invalid signature format");
}
const providedSignature = signature.slice(7); // Remove 'sha256=' prefix
// Compute HMAC signature using the JSON body
const hmac = crypto.createHmac("sha256", signingSecret);
hmac.update(JSON.stringify(body));
const computedSignature = hmac.digest("hex");
// Use timing-safe comparison
const isValid = crypto.timingSafeEqual(
Buffer.from(providedSignature, "hex"),
Buffer.from(computedSignature, "hex")
);
if (!isValid) {
throw new Error("Signature verification failed");
}
}
Use the signing secret you provided when registering the webhook.
OAuth 2.0 Authorization
In addition to signature verification, you may secure your webhook endpoint using OAuth 2.0. This is optional and must be manually configured on both your application and your Xemelgo account.
Handle Webhook Events
Example Webhook Handler Using Express in Node.js
const express = require("express");
const crypto = require("crypto");
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.XEMELGO_WEBHOOK_SECRET;
function verifyWebhookSignature(signature, body, secret) {
if (!signature?.startsWith("sha256=")) {
throw new Error("Invalid signature format");
}
const expectedSignature = signature.slice(7);
const hmac = crypto.createHmac("sha256", secret);
hmac.update(JSON.stringify(body));
const computedSignature = hmac.digest("hex");
if (
!crypto.timingSafeEqual(
Buffer.from(expectedSignature, "hex"),
Buffer.from(computedSignature, "hex")
)
) {
throw new Error("Signature verification failed");
}
}
app.post("/webhooks", (req, res) => {
const signature = req.headers["xemelgo-signature"];
try {
verifyWebhookSignature(signature, req.body, WEBHOOK_SECRET);
} catch (err) {
console.error("Webhook verification failed:", err.message);
return res.status(401).json({ error: "Unauthorized" });
}
const { topic, data } = req.body;
console.log(`Received webhook: ${topic}`, data);
switch (topic) {
case "inventory.cycle_count":
// Handle inventory cycle count event
break;
default:
console.log(`Unhandled event: ${topic}`);
}
res.json({ received: true });
});
app.listen(3000, () => {
console.log("Webhook server running on port 3000");
});
Best Practices
Respond Quickly
Return a 2xx status code after receiving the webhook. If you need to perform long-running operations, acknowledge the webhook immediately and process the event asynchronously:
app.post("/webhooks", async (req, res) => {
// Verify signature first
try {
verifyWebhookSignature(
req.headers["xemelgo-signature"],
req.body,
WEBHOOK_SECRET
);
} catch (err) {
return res.status(401).json({ error: "Unauthorized" });
}
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookEvent(req.body).catch(console.error);
});
Build Idempotent Event Handlers
Webhook endpoints might occasionally receive the same event more than once. Protect against duplicate processing by making your event handler idempotent. You can use the id from the event data to create a unique idempotency key:
const processedEvents = new Set();
app.post("/webhooks", (req, res) => {
const { id, topic, data } = req.body;
// Check if we've already processed this event
if (processedEvents.has(id)) {
return res.status(200).json({ received: true, duplicate: true });
}
// Process the event
processEvent(topic, data);
// Mark as processed
processedEvents.add(id);
res.status(200).json({ received: true });
});
Handle Failures Gracefully
If your endpoint returns a non-2xx status code or times out, Xemelgo automatically retries delivery. Ensure your endpoint can handle retries gracefully and avoid processing duplicate events.
Important Notes
- Always verify the signature before processing webhook events
- Use timing-safe comparison to prevent timing attacks
- The signature is computed over the JSON stringified request body
- Respond with a 2xx status code within 5 seconds to acknowledge receipt
- Make your event handlers idempotent to handle duplicate deliveries
- Process long-running operations asynchronously after acknowledging the webhook