Skip to content

Webhooks

Webhooks let your application receive HTTP callbacks when signing events occur in TheTerms.

  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 2 more times with exponential backoff (3 attempts total)

Webhook creation takes a url only — there is no event-filtering field. Every active webhook receives all events.

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"
}'

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"
}
}
EventWhen it fires
signing.request.createdA signing request is sent (including via bulk/batch import)
signing.request.viewedThe signer opens the signing page
signing.request.completedThe signer submits a decision — accepted or rejected

There is no event for a document being published or archived, and no separate “expired” event.

Every webhook delivery sends a JSON body:

{
"event": "signing.request.completed",
"timestamp": "2026-02-21T10:15:00.000Z",
"data": {
"requestId": "sr_abc123...",
"signerEmail": "jane@example.com",
"signerName": "Jane Doe",
"versionId": "doc_xyz789..."
}
}

Every webhook request includes an X-Webhook-Signature header in the form sha256=<hex-digest> — an HMAC-SHA256 signature of the raw request body, hex-encoded. Strip the sha256= prefix before comparing:

import crypto from "node:crypto";
function verifyWebhook(payload, signatureHeader, secret) {
const signature = signatureHeader.replace(/^sha256=/, "");
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
}
// In your webhook handler:
app.post("/webhooks/theterms", (req, res) => {
const signatureHeader = req.headers["x-webhook-signature"];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signatureHeader,
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, TheTerms retries:

AttemptDelay before it
1st (original send)
2nd (1st retry)4 seconds
3rd (2nd retry, final)16 seconds

3 total attempts. After the 3rd fails, the delivery is marked FAILED — there is currently no API endpoint to inspect delivery history.

List webhooks GET /api/v1/webhooks

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

Returns all webhook endpoints for the organisation. The secret is not included in list responses — only on creation.

Response 200:

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

Create a webhook POST /api/v1/webhooks

Section titled “Create a webhook /api/v1/webhooks”
FieldTypeRequiredDescription
urlstringYeshttp:// or https:// URL to receive events — must resolve to a public, routable address
{ "url": "https://your-server.com/webhooks/theterms" }

Response 201: Webhook object (same shape as create’s response above, including secret). The secret is shown here and never again — save it immediately for signature verification.

Error cases:

StatusCause
400URL fails to parse, isn’t http/https, or resolves to a private/internal address
403Webhook limit reached for the organisation’s plan (TIER_LIMIT)

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 webhook endpoints per organisation depends on your plan:

TierMax webhooks
Free3
Individual10
Team / Pro / EnterpriseUnlimited

Every active webhook receives every event — there is no per-webhook event filtering.