Skip to content

Webhooks

Webhooks push the events of your organization to your HTTPS endpoint as they happen: a document is signed, a registered mail is delivered, a task is created… Use them instead of polling.

  • Organizations: an administrator (integrations.manage) creates subscriptions in Settings → Integrations → Webhooks: an HTTPS URL, a description and the events to receive. The signing secret (whsec_…) is shown once.
  • Partner applications: subscriptions are attached to the application by MyCLM. They receive the events of every organization that granted the application access.

A subscription can be disabled and enabled again, its secret rotated (the new secret applies immediately to the next deliveries), and Send a test event delivers a webhook.test event on demand.

Each event is POSTed as JSON to every subscription listening to its type:

POST /webhooks/mylegitech HTTP/1.1
Content-Type: application/json
User-Agent: MyCLM-Webhooks/1
X-MLT-Signature: t=1790849566,v1=5d2f8a0c7e1b4a9f3c6d8e2b7a1f0c9d4e8b3a6f2c1d7e9b0a4f8c3d6e2b1a7f
X-MLT-Event-Id: 0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e
X-MLT-Event-Type: signature.completed
X-MLT-Delivery-Id: 0192f3b0-1301-7a2c-8d4e-6f7a8b9c0d1e
X-MLT-Livemode: true
{"id":"0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e","type":"signature.completed","apiVersion":"1","createdAt":"2026-10-01T10:12:45.000Z","providerId":"0192f3a1-7c2e-7b4d-9a61-100000000001","orgId":"0192f3a1-7c2e-7b4d-9a61-200000000001","livemode":true,"data":{"requestId":"0192f3a1-7c2e-7b4d-9a61-400000000001","title":"Bail - Lot 12"}}
Field Description
id Event id. Identical across retries and replays: deduplicate on it.
type Event type, see the catalog below.
apiVersion Payload version, currently "1".
createdAt When the event occurred.
providerId, orgId Provider and organization of the event.
livemode true for a production event, false for an event of a test organization.
data Identifiers and business status only. Read the details through the API, with your own rights.
Header Description
X-MLT-Signature t=<unix seconds>,v1=<hex HMAC-SHA256>, see Verify the signature.
X-MLT-Event-Id Same as id.
X-MLT-Event-Type Same as type.
X-MLT-Delivery-Id One delivery per event and subscription.
X-MLT-Livemode Same as livemode: true or false.

Deliveries are at least once and not ordered: use createdAt, and re-read the resource when the order matters.

The signature is an HMAC-SHA256 of <t>.<raw body> with the signing secret of the subscription (the whole whsec_… string). Verify it on the raw bytes of the body, before any JSON parsing, and reject timestamps older than 5 minutes to block replays.

import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
const SECRET = process.env.MLT_WEBHOOK_SECRET!; // whsec_…
const TOLERANCE_S = 5 * 60;
export function verifySignature(rawBody: Buffer, header: string | undefined, secret: string): boolean {
if (!header) return false;
const parts = new Map(header.split(',').map((part) => {
const index = part.indexOf('=');
return [part.slice(0, index).trim(), part.slice(index + 1)] as const;
}));
const t = Number(parts.get('t'));
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > TOLERANCE_S) return false;
const expected = Buffer.from(createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex'));
const given = Buffer.from(parts.get('v1') ?? '');
return given.length === expected.length && timingSafeEqual(given, expected);
}
const app = express();
app.post('/webhooks/mylegitech', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifySignature(req.body, req.get('X-MLT-Signature'), SECRET)) return res.sendStatus(400);
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(204); // acknowledge first, then process asynchronously
queue.push(event); // your own job queue, deduplicated by event.id
});

Answer any 2xx status within 10 seconds, then process the event in the background. Anything else is a failure: another status, a timeout, a network error, or a redirect (redirects are not followed). The first 500 characters of your response are kept in the delivery log to help you debug.

A failed delivery is retried with exponential backoff, 9 attempts over about 23 hours:

Attempt Delay after the previous attempt
1 Immediately
2 30 seconds
3 2 minutes
4 10 minutes
5 30 minutes
6 1 hour
7 3 hours
8 6 hours
9 12 hours

After the last attempt the delivery is given up (dead). After 15 consecutive failed attempts, the subscription is disabled automatically and the administrators of the organization are notified by email; fix your endpoint, enable the subscription again, then replay the missed deliveries.

The delivery log of a subscription (Settings → Integrations → Webhooks) lists every delivery of the last 30 days with its status, attempts, response code and payload. Replay sends a delivery again immediately with a fresh retry budget, and the same event id.

Generated from the API source. The schema of each payload is also in the API reference, under Webhooks.

EventWhen
signature.signedA signer signed
signature.completedSignature request completed
signature.declinedA signer declined
signature.expiredSignature request expired
signature.canceledSignature request canceled
registered_mail.deliveredRegistered mail delivered
registered_mail.refusedRegistered mail refused
registered_mail.neglectedRegistered mail neglected
registered_mail.failedRegistered mail failed
task.createdTask created
task.completedTask occurrence completed
member.addedMember added
contact.createdContact created
contact.updatedContact updated
webhook.testTest event

signature.signed

Sent each time one signer of a signature request signs. The request is completed once every signer has signed (signature.completed).

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "signature.signed",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"requestId": "0192f3a1-7c2e-7b4d-9a61-400000000001",
"title": "Bail - Lot 12",
"signerId": "0192f3a1-7c2e-7b4d-9a61-400000000011",
"signerName": "Marie Durand"
}
}

Schema in the API reference →

signature.completed

Every signer signed and the signed document is available (GET /v1/signature-requests/{id}/document?variant=signed).

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "signature.completed",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"requestId": "0192f3a1-7c2e-7b4d-9a61-400000000001",
"title": "Bail - Lot 12"
}
}

Schema in the API reference →

signature.declined

A signer declined to sign: the request is declined and nobody else can sign it.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "signature.declined",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"requestId": "0192f3a1-7c2e-7b4d-9a61-400000000001",
"title": "Bail - Lot 12",
"signerId": "0192f3a1-7c2e-7b4d-9a61-400000000012",
"signerName": "Paul Martin",
"reason": "Le montant du dépôt de garantie est erroné."
}
}

Schema in the API reference →

signature.expired

The request reached its expiry date before every signer signed: it is expired and can no longer be signed.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "signature.expired",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"requestId": "0192f3a1-7c2e-7b4d-9a61-400000000001",
"title": "Bail - Lot 12"
}
}

Schema in the API reference →

signature.canceled

The request was canceled by its organization: it is canceled and can no longer be signed.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "signature.canceled",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"requestId": "0192f3a1-7c2e-7b4d-9a61-400000000001",
"title": "Bail - Lot 12"
}
}

Schema in the API reference →

registered_mail.delivered

The recipient accepted the registered mail. The delivery proof becomes downloadable.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "registered_mail.delivered",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"mailId": "0192f3a1-7c2e-7b4d-9a61-500000000001",
"itemId": "0192f3a1-7c2e-7b4d-9a61-500000000011",
"status": "delivered",
"reference": "LRE-2026-0142",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002"
}
}

Schema in the API reference →

registered_mail.refused

The recipient explicitly refused the registered mail.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "registered_mail.refused",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"mailId": "0192f3a1-7c2e-7b4d-9a61-500000000001",
"itemId": "0192f3a1-7c2e-7b4d-9a61-500000000011",
"status": "refused",
"reference": "LRE-2026-0142",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002"
}
}

Schema in the API reference →

registered_mail.neglected

The recipient did not accept the registered mail within the legal period.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "registered_mail.neglected",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"mailId": "0192f3a1-7c2e-7b4d-9a61-500000000001",
"itemId": "0192f3a1-7c2e-7b4d-9a61-500000000011",
"status": "neglected",
"reference": "LRE-2026-0142",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002"
}
}

Schema in the API reference →

registered_mail.failed

The item could not be delivered: the email bounced (status: bounced) or the vendor refused the item (status: failed).

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "registered_mail.failed",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"mailId": "0192f3a1-7c2e-7b4d-9a61-500000000001",
"itemId": "0192f3a1-7c2e-7b4d-9a61-500000000011",
"status": "bounced",
"reference": "LRE-2026-0142",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002"
}
}

Schema in the API reference →

task.created

A task was created (by a person, an integration or automatically).

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "task.created",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"taskId": "0192f3a1-7c2e-7b4d-9a61-700000000001",
"title": "Relancer Marie Durand - état des lieux",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002",
"assigneeId": "0192f3a1-7c2e-7b4d-9a61-300000000003",
"startAt": "2026-10-06T07:00:00.000Z"
}
}

Schema in the API reference →

task.completed

An occurrence of a task was marked as done. Recurring tasks send one event per completed occurrence.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "task.completed",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"taskId": "0192f3a1-7c2e-7b4d-9a61-700000000001",
"title": "Relancer Marie Durand - état des lieux",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002",
"occurrenceAt": "2026-10-06T07:00:00.000Z",
"completedBy": "0192f3a1-7c2e-7b4d-9a61-300000000003"
}
}

Schema in the API reference →

member.added

A user was attached to a node of the organization (invitation, provisioning). newAccount is true when the user account was created by this operation.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "member.added",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"userId": "0192f3a1-7c2e-7b4d-9a61-300000000003",
"nodeId": "0192f3a1-7c2e-7b4d-9a61-200000000004",
"newAccount": true
}
}

Schema in the API reference →

contact.created

A shared contact was created (manually, by import or through the API). Private contacts never trigger webhooks.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "contact.created",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"contactId": "0192f3a1-7c2e-7b4d-9a61-600000000001",
"kind": "person",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002"
}
}

Schema in the API reference →

contact.updated

A shared contact was updated. Private contacts never trigger webhooks.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "contact.updated",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"contactId": "0192f3a1-7c2e-7b4d-9a61-600000000001",
"kind": "person",
"entityId": "0192f3a1-7c2e-7b4d-9a61-200000000002"
}
}

Schema in the API reference →

webhook.test

Sent by the Send a test event action of a subscription (Settings > Integrations > Webhooks). It cannot be subscribed to.

{
"id": "0192f3b0-12c4-7e8a-b3d1-5f6a7b8c9d0e",
"type": "webhook.test",
"apiVersion": "1",
"createdAt": "2026-10-01T10:12:45.000Z",
"providerId": "0192f3a1-7c2e-7b4d-9a61-100000000001",
"orgId": "0192f3a1-7c2e-7b4d-9a61-200000000001",
"data": {
"message": "Test event from MyCLM",
"subscriptionId": "0192f3a1-7c2e-7b4d-9a61-900000000001"
}
}

Schema in the API reference →