Setting up webhooks

Create a webhook endpoint, verify the HMAC signature, and handle retries and failures.

Updated 16 Jun 2026

Webhooks push event notifications to your server the moment something happens in Clment — a review completes, a redline is ready, a teammate is mentioned — so you don’t have to poll. This guide covers creating a webhook, verifying the signature on each delivery, and handling retries.

For the full list of events and their payloads, see the webhook event catalog.

1. Create the webhook

An organization admin configures webhooks under Settings → API & Integrations → Webhooks:

  1. Click Add webhook.
  2. Enter a label and the target URL — an HTTPS endpoint on your server that accepts POST.
  3. Choose which events to receive. Leave them all unchecked to subscribe to every event, current and future.
  4. Click Add webhook. Clment shows your signing secret once — copy it now and store it in your secret manager. It’s used to verify deliveries; you can’t retrieve it later (but you can rotate it).

2. Receive a delivery

Clment sends a POST with a JSON body in the standard envelope:

{
  "id": "evt_8f3a…",
  "type": "review.completed",
  "occurredAt": "2026-06-16T14:32:07.120Z",
  "organizationId": "org_a1b2…",
  "payload": { }
}

Along with these headers:

Content-Type: application/json
User-Agent: Clment-Webhook/1.0
X-Clment-Event: review.completed
X-Clment-Event-Id: evt_8f3a…
X-Clment-Delivery-Timestamp: 2026-06-16T14:32:08.000Z
X-Clment-Signature: sha256=2c9f…

Respond with any 2xx status to acknowledge. Anything else (or a timeout past 10 seconds) is treated as a failure and retried.

3. Verify the signature

Always verify the signature before trusting a delivery. It proves the request genuinely came from Clment and wasn’t forged or tampered with.

The signature is HMAC-SHA256(secret, rawRequestBody), hex-encoded, in the X-Clment-Signature header as sha256=<hex>. Compute the same HMAC over the raw request body bytes (not a re-serialized object — key order matters) and compare with a constant-time equality check.

Node.js (Express)

import express from 'express';
import crypto from 'crypto';

const app = express();
const SECRET = process.env.CLMENT_WEBHOOK_SECRET;

// Capture the raw body so the HMAC matches byte-for-byte.
app.use('/clment-webhook', express.raw({ type: 'application/json' }));

app.post('/clment-webhook', (req, res) => {
  const signature = req.header('X-Clment-Signature') || '';
  const expected =
    'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');

  const ok =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

  if (!ok) {
    return res.status(401).send('bad signature');
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Dedup on event.id, then handle event.type …
  res.sendStatus(200);
});

Python (Flask)

import hmac, hashlib
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["CLMENT_WEBHOOK_SECRET"].encode()

@app.post("/clment-webhook")
def webhook():
    signature = request.headers.get("X-Clment-Signature", "")
    expected = "sha256=" + hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected):
        abort(401)
    event = request.get_json()
    # Dedup on event["id"], then handle event["type"] …
    return "", 200

4. Deduplicate

Retries re-deliver the same id. Your handler must be idempotent — record processed event ids and skip duplicates, or make the downstream effect naturally idempotent. Never assume exactly-once delivery; design for at-least-once.

5. Retries and failures

If your endpoint doesn’t return 2xx (or times out after 10 seconds), Clment retries with exponential backoff:

AttemptDelay after previous
1immediate
21 second
35 seconds
430 seconds
55 minutes
630 minutes

After the 6th failed attempt the delivery is dead-lettered and not retried again. If 5 deliveries dead-letter back-to-back, Clment auto-pauses the webhook (its status shows failed with a date). Fix your endpoint, then resume it from the Settings list — new events flow again. Events that occurred while a webhook was paused are not replayed.

Inspect recent attempts any time: click Log on a webhook to see the last 100 deliveries from the past 7 days, with status codes and error messages.

6. Test it

Use the Test button on each webhook to fire a synthetic contract.created event at your endpoint and see the response inline. The test is signed exactly like a real delivery, so a passing test confirms your signature verification works. The test event is not written to the event queue — it won’t reach other consumers or show up as a real event.

Rotating the signing secret

Click Rotate on a webhook to generate a new signing secret (shown once). Update your endpoint to verify against the new secret as soon as you copy it — deliveries already in flight that were signed with the old secret will fail verification on your end and dead-letter. Rotate immediately if you suspect the secret has leaked.

Security checklist

  • ✅ Verify the HMAC signature on every request with a constant-time comparison.
  • ✅ Use HTTPS for your endpoint (required outside development).
  • ✅ Deduplicate on event.id.
  • ✅ Respond fast (under 10s) — do heavy work asynchronously after acknowledging.
  • ✅ Store the signing secret in a secret manager, never in code.

Still have questions?

Instant article search