Gareth Wilson Gareth Wilson

Guide to Clio Webhooks: Features and Best Practices

Published


Clio webhooks notify your systems about legal-practice activity: a matter is created, a contact is updated, an activity or bill changes. If you're building on Clio Manage, webhooks are how you react to case and client events without polling.

This guide covers how Clio webhooks work, the split model/events subscription design, how to verify the X-Hook-Signature, the mandatory renewal (webhooks expire), and the best practices for production.

What are Clio webhooks?

Clio Manage webhooks are HTTP POSTs delivered to a URL you register, each signed with an HMAC-SHA256 in the X-Hook-Signature header, computed over the raw body with the shared_secret you supply when creating the webhook.

Clio's subscription design is split: you pick a model (like matter or contact) and an array of events (created, updated, deleted). Payloads reference these as model.action (for example matter.created).

Clio webhook features

FeatureDetails
ConfigurationPOST /api/v4/webhooks on your region's host (US app.clio.com, EU eu.app.clio.com, CA ca.app.clio.com, AU au.app.clio.com)
Signature headerX-Hook-Signature
Signature schemelowercase-hex HMAC-SHA256 over the raw body, keyed with the shared_secret you supply
Modelone of activity, bill, calendar_entry, communication, contact, matter, task
Eventsarray of created / updated / deleted
EnablementMay be created pending and auto-enable within ~90s (no handshake echo required)
ExpiryDefault 3 days; extend via expires_at (up to ~31 days); renewal mandatory
SDKNone (plain REST)

Common events

Payloads reference model.action compositions:

EventFires when
matter.createdA matter is created
matter.updatedA matter is updated
contact.createdA contact is created
contact.updatedA contact is updated
task.updatedA task is updated

You compose these by choosing a model and its events at subscription time.

Setting up Clio webhooks

Create a webhook with POST /api/v4/webhooks on your region's Clio host, providing an HTTPS url, a model, the events array, the fields you want, a shared_secret you generate, and an optional expires_at.

Use the host that matches your app's region. Clio is regionalized (US app.clio.com, EU eu.app.clio.com, CA ca.app.clio.com, AU au.app.clio.com), and an app only exists on its own region's host. Call the wrong one and you get invalid_client, which reads like a bad secret and sends you debugging in the wrong place, when the real fix is the host.

Because Clio doesn't track usage, you must renew (or re-create) the webhook before it expires, the default lifetime is just 3 days, extendable via expires_at to about 31 days.

The webhook may auto-enable. A new subscription can start in a pending state and then flip to enabled on its own within about 90 seconds, and start delivering, without any handshake request ever reaching your endpoint. So Clio's "not enabled until the handshake succeeds" isn't absolute: don't block your rollout on implementing an echo you may never receive.

Securing Clio webhooks

Each delivery carries an X-Hook-Signature header: a lowercase-hex HMAC-SHA256 of the raw request body, keyed with the shared_secret you supplied. Recompute it over the raw bytes and compare in constant time, this matches Clio's header exactly (verified against real deliveries, not inferred).

const crypto = require("crypto");

const SECRET = process.env.CLIO_SHARED_SECRET; // you supplied this at creation

function verify(rawBody, signature) {
  const expected = crypto.createHmac("sha256", SECRET).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-hook-signature"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

The same check in Python:

import hashlib
import hmac
import os

SECRET = os.environ["CLIO_SHARED_SECRET"].encode()


def verify(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

Verify against the raw body, before any JSON parsing, or the recomputed HMAC won't match.

Clio webhook limitations and pain points

Webhooks expire and must be renewed

The Problem: A Clio webhook expires (default 3 days, up to ~31 via expires_at), and Clio doesn't track usage, so if you don't renew or re-create it before expiry, delivery silently stops.

Why It Happens: Clio time-boxes webhooks and requires explicit renewal.

Workarounds:

  • Set a long expires_at and still renew/re-create from a scheduled job well before expiry.

How Hookdeck Can Help: Hookdeck's observability surfaces a drop in delivery volume, so a lapsed Clio webhook shows up as an alert rather than silent data loss.

The split model/events design

The Problem: You subscribe by model plus an events array, and payloads use model.action. Expecting flat event names (or the wrong composition) mis-routes events.

Why It Happens: Clio separates the resource model from the action events.

Workarounds:

  • Compose model + events at subscription time, and branch on model.action in the payload.

How Hookdeck Can Help: Hookdeck's filters route on the model.action you receive, so composition is handled in one place.

Regional hosts are mandatory

The Problem: Clio is regionalized, and an app only exists on its own region's host. Create a webhook (or authenticate) against the wrong host and Clio returns invalid_client, which looks exactly like a bad secret and sends you debugging credentials when the real problem is the URL.

Why It Happens: Clio partitions accounts by region (US, EU, CA, AU), each on its own host, with no cross-region routing.

Workarounds:

  • Use the host matching your app's region (US app.clio.com, EU eu.app.clio.com, CA ca.app.clio.com, AU au.app.clio.com) for both authentication and webhook creation.

How Hookdeck Can Help: Hookdeck gives you one stable ingestion URL regardless of Clio's region, so your endpoint configuration doesn't shift per region.

Duplicates

The Problem: Retries can deliver the same event more than once.

Why It Happens: At-least-once delivery favors eventual delivery.

Workarounds:

  • Dedupe on an event id and make side effects idempotent.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge. See our guide to webhook idempotency.

Best practices

Use your region's host

Create webhooks and authenticate on the Clio host for your app's region (US/EU/CA/AU). A wrong-region call returns invalid_client, which masquerades as a bad secret.

Verify the lowercase-hex signature over the raw body

Compute a lowercase-hex HMAC-SHA256 over the raw body with your shared_secret and compare in constant time.

Renew before expiry

Set expires_at and renew/re-create before the (default 3-day) expiry; don't rely on usage keeping it alive.

Acknowledge fast, process asynchronously

Return 200 quickly and defer work to a queue. See why to process webhooks asynchronously.

Compose model + events and dedupe

Subscribe with the right model/events, branch on model.action, and dedupe so retries don't double-process.

Make Clio webhooks production-ready

Hookdeck verifies X-Hook-Signature, surfaces lapsed subscriptions, deduplicates, and durably queues every event

Conclusion

Clio webhooks verify with a lowercase-hex X-Hook-Signature HMAC over the raw body, keyed with the shared_secret you supply, using a split model + events subscription design. Two sharp edges to plan around: use your region's Clio host (a wrong-region call returns a misleading invalid_client), and renew before the default 3-day expiry. Verify against the raw body, renew ahead of expiry, and dedupe.

Hookdeck verifies the signature, surfaces lapsed subscriptions, deduplicates, and durably queues every event at the edge, so your app processes verified, unique events without silently losing delivery.

Get started with Hookdeck for free and handle Clio webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.