Receive real-time notifications when events happen in your Offly organisation.
Webhooks are HTTP callbacks that notify your application when events occur in Offly. Instead of polling the API for changes, you register a URL and Offly sends a POST request to that URL whenever a subscribed event fires.
| Event | Description |
|---|---|
| leave_request.created | A new leave request was submitted |
| leave_request.approved | A leave request was approved |
| leave_request.declined | A leave request was declined |
| leave_request.cancelled | A leave request was cancelled |
| user.created | A new user was added to the organisation |
| user.updated | A user's profile was updated |
| department.created | A new department was created |
| public_holiday.created | A public holiday was added |
Each webhook delivery includes an event type, timestamp, and the relevant data:
{
"id": "wh_evt_01HZ3K9M7PQRS",
"type": "leave_request.approved",
"created_at": "2026-05-20T14:30:00Z",
"data": {
"id": "lr_8f14e45f-ceea-467f",
"user_id": "usr_a1b2c3d4",
"leave_type": "annual_leave",
"start_date": "2026-07-01",
"end_date": "2026-07-05",
"status": "approved",
"approved_by": "usr_e5f6g7h8"
}
}Every webhook delivery includes an X-Offly-Signature header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret. Always verify this signature to ensure the request is from Offly.
import crypto from "crypto";
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post("/webhooks/offly", (req, res) => {
const signature = req.headers["x-offly-signature"];
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.OFFLY_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send("Invalid signature");
}
// Process the event
const event = req.body;
console.log(`Received: ${event.type}`);
res.status(200).send("OK");
});If your endpoint returns a non-2xx status code or times out (30 second timeout), Offly will retry delivery up to 3 times with exponential backoff:
After all retries are exhausted, the delivery is marked as failed. You can view failed deliveries and manually retry them from Settings → Webhooks.
Use ngrok to expose your local development server to receive webhook deliveries:
# Start your local server on port 3000
npm run dev
# In another terminal, expose it via ngrok
ngrok http 3000
# Use the ngrok URL as your webhook endpoint:
# https://abc123.ngrok.io/webhooks/offly