Skip to content

Webhooks

Webhooks let your application receive HTTP callbacks when events occur in TheTerms — for example, when a document is signed or a signing request expires.

  1. You register a webhook URL in TheTerms (via API or dashboard)
  2. When an event occurs, TheTerms sends an HTTP POST to your URL
  3. Your server processes the payload and responds with 2xx
  4. If delivery fails, TheTerms retries up to 3 times with exponential backoff
Terminal window
curl -X POST "$THETERMS_URL/webhooks" \
-H "X-Api-Key: $THETERMS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/theterms",
"events": ["signing.completed", "signing.viewed"]
}'

Response:

{
"data": {
"id": "018e1234-abcd-7000-8000-000000000040",
"url": "https://your-server.com/webhooks/theterms",
"secret": "whsec_...",
"is_active": true,
"org_id": "018e1234-abcd-7000-8000-000000000000",
"created_at": "2026-02-21T10:00:00.000Z"
}
}
EventDescription
signing.completedA signer completed signing (all mandatory clauses accepted)
signing.viewedA signer opened the signing page
signing.expiredA signing request’s token expired
document.publishedA document version was published
document.archivedA document version was archived

Every webhook delivery sends a JSON body:

{
"event": "signing.completed",
"timestamp": "2026-02-21T10:15:00.000Z",
"data": {
"signingRequestId": "sr_abc123...",
"signerEmail": "jane@example.com",
"signerName": "Jane Doe",
"documentId": "doc_xyz789...",
"responses": [
{ "clauseId": "clause-1", "accepted": true },
{ "clauseId": "clause-2", "accepted": false }
]
}
}

Every webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Verify it to ensure the request came from TheTerms:

import crypto from "node:crypto";
function verifyWebhook(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/theterms", (req, res) => {
const signature = req.headers["x-webhook-signature"];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.THETERMS_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send("Invalid signature");
}
// Process the event
console.log("Event:", req.body.event);
res.status(200).send("OK");
});

If your server returns a non-2xx response or doesn’t respond within 10 seconds:

AttemptDelay
1st retry~2 seconds
2nd retry~4 seconds
3rd retry~8 seconds

After 3 failed retries, the delivery is marked as failed. You can inspect failed deliveries via the API:

Terminal window
curl "$THETERMS_URL/webhooks/{webhookId}/deliveries" \
-H "X-Api-Key: $THETERMS_API_KEY"

List webhooks GET /api/v1/webhooks

Section titled “List webhooks /api/v1/webhooks”

Returns all webhook endpoints for the organisation.

Response 200:

{
"data": [
{
"id": "018e1234-abcd-7000-8000-000000000040",
"url": "https://your-server.com/webhooks/theterms",
"is_active": true,
"secret": "whsec_...",
"org_id": "018e1234-abcd-7000-8000-000000000000",
"created_at": "2026-02-01T09:00:00.000Z"
}
]
}

Create a webhook POST /api/v1/webhooks

Section titled “Create a webhook /api/v1/webhooks”
FieldTypeRequiredDescription
urlstringYesHTTPS URL to receive events
{ "url": "https://your-server.com/webhooks/theterms" }

Response 201: Webhook object (same as list). The secret is shown here and never again — save it immediately for signature verification.

Error cases:

StatusCause
400url is not a valid HTTPS URL
409A webhook with this URL already exists

Update a webhook PUT /api/v1/webhooks/:id

Section titled “Update a webhook /api/v1/webhooks/:id”
FieldTypeDescription
urlstringNew endpoint URL
is_activebooleanEnable (true) or pause (false) delivery
{ "is_active": false }

Response 200: Updated webhook object.

Delete a webhook DELETE /api/v1/webhooks/:id

Section titled “Delete a webhook /api/v1/webhooks/:id”

Permanently removes the webhook and stops all future deliveries.

Response 200:

{ "data": { "success": true } }

Error cases:

StatusCause
404Webhook not found in this organisation
  • Maximum 5 webhook endpoints per organisation
  • Each webhook can subscribe to multiple event types