Webhooks

Receive real-time notifications when events happen in your Offly organisation.

What are webhooks?

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.

Creating a webhook endpoint

  1. Go to Settings → Webhooks
  2. Click Add Endpoint
  3. Enter your HTTPS endpoint URL
  4. Select the events you want to subscribe to
  5. Click Create
  6. Copy your webhook signing secret for signature verification

Available events

EventDescription
leave_request.createdA new leave request was submitted
leave_request.approvedA leave request was approved
leave_request.declinedA leave request was declined
leave_request.cancelledA leave request was cancelled
user.createdA new user was added to the organisation
user.updatedA user's profile was updated
department.createdA new department was created
public_holiday.createdA public holiday was added

Example payload

Each webhook delivery includes an event type, timestamp, and the relevant data:

Webhook Payload
{
  "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"
  }
}

Signature verification

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");
});

Retry behaviour

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:

  • 1st retry: after 1 minute
  • 2nd retry: after 5 minutes
  • 3rd retry: after 30 minutes

After all retries are exhausted, the delivery is marked as failed. You can view failed deliveries and manually retry them from Settings → Webhooks.

Testing webhooks locally

Use ngrok to expose your local development server to receive webhook deliveries:

Terminal
# 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