Skip to main content

Webhooks

Webhooks allow you to receive real-time notifications about key events in your GlycanAge integration. Instead of polling our API for status updates, webhooks will automatically notify your system when important events occur.

Overview

Our webhook system sends HTTP POST requests to your specified endpoint whenever subscribed events occur. Each webhook includes event data and is secured using HMAC authentication to ensure the requests are genuinely from GlycanAge.

Sandbox Simulation

In the sandbox environment the ordering and lab flow is simulated so you can test your webhook integration end-to-end without any physical kits or manual processing. The simulation is driven by your actions, mirroring the real flow:

  1. Place an order — the order is automatically shipped (no manual approval) and partner_order.shipped is fired with generated kit codes and tracking links.

  2. Assign kits to a patient — creating an assignment drives the lab lifecycle for those kits:

    unit.in_lab → unit.in_analysis → result.ready

Placing an order alone will not produce results — you must assign its kits to a patient to advance through the lab stages.

  • Kits, tracking codes and reports are generated with fake but structurally valid data.
  • Each step is emitted after a short delay, so you can observe the transitions as they happen.

This simulation is only active in the sandbox. In production these events reflect real warehouse, laboratory and analysis activity.

Setting Up Webhooks

1. Create a Webhook Endpoint

First, create an endpoint on your server that can receive HTTP POST requests. This endpoint must be accessible via HTTPS.

// Example webhook endpoint (Node.js/Express)
app.post("/webhooks/glycanage", (req, res) => {
const signature = req.headers["x-webhook-signature"];
const payload = JSON.stringify(req.body);

// Verify the webhook signature (see authentication section)
if (verifySignature(payload, signature, webhookSecret)) {
const event = req.body;

// Process the event
console.log("Received event:", event.event);

// Respond with 200 to acknowledge receipt
res.status(200).send("OK");
} else {
res.status(401).send("Unauthorized");
}
});

2. Register Your Webhook

Use the API to register your webhook endpoint:

POST /webhooks
Authorization: Basic {base64_encoded_token}
Content-Type: application/json

{
"url": "https://your-domain.com/webhooks/glycanage",
"events": [
"partner_order.shipped",
"unit.in_lab",
"unit.in_analysis",
"result.ready"
],
"secret": "your-secure-32-character-or-longer-secret"
}
  • url — Must be an HTTPS URL.
  • events — An array of 1–4 event types to subscribe to.
  • secret — A string between 32 and 128 characters used for HMAC signature verification.

Authentication

All webhook requests are authenticated using HMAC-SHA256. We compute a signature using your provided secret and include it in the X-Webhook-Signature header as a hex digest.

Verifying Webhook Signatures

const crypto = require("crypto");

function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature),
);
}
import hmac
import hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()

return hmac.compare_digest(signature, expected)

Webhook Payload

All webhook payloads follow this structure:

{
"event": "<event_type>",
"timestamp": "2024-08-27T12:00:00.000Z",
"data": { ... }
}
  • event — The event type string.
  • timestamp — ISO 8601 timestamp of when the event was fired.
  • data — Event-specific data (varies by event type).

The event type is also sent in the X-Webhook-Event header.

Event Types

partner_order.shipped

Triggered when a partner order is shipped from the warehouse.

{
"event": "partner_order.shipped",
"timestamp": "2024-08-27T12:00:00.000Z",
"data": {
"order_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"kits": [
{
"kitCode": "KIT001234",
"trackingCode": "1Z999AA10123456784",
"trackingLink": "https://tracking.example.com/1Z999AA10123456784"
}
]
}
}

unit.in_lab

Triggered when a kit is received at the GlycanAge laboratory.

note

Active in the sandbox (fired by the simulation when you assign kits). In production this event is not yet live and will be enabled in a future release.

unit.in_analysis

Triggered when a kit enters the analysis phase at the laboratory.

note

Active in the sandbox (fired by the simulation when you assign kits). In production this event is not yet live and will be enabled in a future release.

result.ready

Triggered when a glycan analysis is complete and the report is available.

note

Active in the sandbox (fired by the simulation when you assign kits). In production this event is not yet live and will be enabled in a future release.

Webhook Management

List Your Webhooks

GET /webhooks
Authorization: Basic {base64_encoded_token}

Delete a Webhook

DELETE /webhooks/{webhookId}
Authorization: Basic {base64_encoded_token}

Best Practices

1. Respond Quickly

Your webhook endpoint should respond with a 200 status code within 10 seconds.

2. Handle Duplicate Events

Implement idempotency in your webhook handler. Events may occasionally be delivered more than once.

const processedEvents = new Set();

app.post("/webhooks/glycanage", (req, res) => {
const eventId = `${req.body.event}-${req.body.timestamp}`;

if (processedEvents.has(eventId)) {
return res.status(200).send("Already processed");
}

processEvent(req.body);
processedEvents.add(eventId);

res.status(200).send("OK");
});

3. Validate Event Data

Always validate the structure and content of webhook events before processing:

function validateEvent(event) {
if (!event.event || !event.timestamp || !event.data) {
throw new Error("Invalid event structure");
}

const validTypes = [
"partner_order.shipped",
"unit.in_lab",
"unit.in_analysis",
"result.ready",
];

if (!validTypes.includes(event.event)) {
throw new Error("Unknown event type");
}

return true;
}

4. Handle Errors Gracefully

app.post("/webhooks/glycanage", async (req, res) => {
try {
const event = req.body;
validateEvent(event);

await processEvent(event);
res.status(200).send("OK");
} catch (error) {
console.error("Webhook processing error:", error);

// Return 200 for validation errors to prevent retries
if (
error.message.includes("Invalid") ||
error.message.includes("Unknown")
) {
res.status(200).send("Invalid event");
} else {
res.status(500).send("Processing failed");
}
}
});

Testing Webhooks

For testing purposes, you can use tools like:

  • ngrok: Expose your local development server to the internet
  • webhook.site: Generate temporary webhook URLs for testing
  • Postman: Mock webhook requests with sample payloads

Example Test Setup

  1. Use ngrok to expose your local webhook endpoint:

    ngrok http 3000
  2. Register the ngrok URL as your webhook endpoint:

    {
    "url": "https://abc123.ngrok.io/webhooks/glycanage",
    "events": ["partner_order.shipped"],
    "secret": "test-secret-key-for-development-env"
    }
  3. Test with sample data to ensure your handler works correctly.

Troubleshooting

Common Issues

Webhook not receiving events:

  • Verify webhook is approved by GlycanAge team
  • Check that your endpoint returns 200 status codes
  • Ensure your URL is accessible from the internet

Authentication failures:

  • Verify your HMAC signature verification logic
  • Check that you're using the correct secret
  • Ensure you're computing the signature on the raw request body

Missed events:

  • Implement proper error handling and logging
  • Check webhook delivery logs (contact support for access)
  • Consider implementing a fallback polling mechanism for critical events

For additional support, contact support@glycanage.com with your webhook configuration and any error logs.