Preskočiť na obsah

Webhooks

Tento obsah zatiaľ nie je dostupný vo vašom jazyku.

When a case changes, TrustInk POSTs an event to the callback.url you gave in POST /cases. Webhooks are the recommended way to learn that a case is complete; see real-time events for live updates in a browser.

type When
participant-signed a participant signed; participantId names them
case-completed everyone signed and the signed PDF is ready; signedDocument.url links it
case-failed everyone signed, but the document could not be signed; the case is FAILED
case-expired validUntil passed before everyone signed
case-cancelled you cancelled the case
POST <callback.url>
Content-Type: application/json
Trustink-Event: case-completed
Trustink-Delivery: 2f1c9a7e-5b0d-4c61-9d1e-7a3b8c2e4f10
Trustink-Signature: t=1790175600,v1=5c3f…
{
"id": "7b1e4c2a-…",
"type": "case-completed",
"occurredAt": "2026-09-23T15:40:00Z",
"tenantId": "d47b9463-…",
"caseId": "49d4ae62-…",
"status": "DONE",
"data": {"orderId": 4711},
"signedDocument": {"url": "https://…"},
"evidenceDocument": {"url": "https://…"}
}
  • id is the same on every attempt to deliver one event, so you can drop duplicates. Trustink-Delivery is new on every attempt.
  • status is the case status when the event is delivered. data is the callback.data of the case, unchanged: put your own reference (an order number, a record id) there.
  • participantId comes with participant-signed. signedDocument.url comes once the case is DONE; it works for an hour and downloads as <title>-signed.pdf. evidenceDocument.url, the sealed evidence summary of the signing, comes with it and downloads as <title>-evidence.pdf.

The callback object of POST /cases:

{
"callback": {
"url": "https://example.com/trustink/webhook",
"authType": "NONE",
"signingSecret": "replace-with-32-to-128-random-characters",
"data": {"orderId": 4711}
}
}

callback.authType decides what the request carries, in addition to the signature:

authType Request
NONE nothing
BASIC Authorization: Basic base64(clientId:clientSecret)
API_KEY <apiKeyHeader>: <apiKeyValue>, e.g. X-Api-Key: …
OAUTH_CLIENT_CREDENTIALS Authorization: Bearer <access_token>, a token from tokenUrl

For OAUTH_CLIENT_CREDENTIALS TrustInk POSTs grant_type=client_credentials (plus scope and audience when the callback sets them) as application/x-www-form-urlencoded to tokenUrl, an https URL. The client authenticates with HTTP Basic (client_secret_basic, the default), or with client_id and client_secret in the form when tokenAuthMethod is post. The answer must carry access_token, and token_type Bearer if any; expires_in defaults to an hour.

The token is cached until 60 seconds before it expires. When your endpoint answers 401, TrustInk fetches a new token and sends once more right away. A token endpoint that is down (5xx, 408, 429, no answer) counts as a delivery to retry; one that refuses the credentials fails the delivery.

With callback.signingSecret set (32 to 128 characters), every request carries

Trustink-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(signingSecret, t + "." + raw body)>

Verify it over the raw body, before parsing it, and reject old timestamps against replays. Compare in constant time.

verify.js
const {createHmac, timingSafeEqual} = require('node:crypto');
function verify(secret, rawBody, header, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map(part => part.split('=', 2)));
const t = Number(parts.t);
if (!parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1, 'hex');
return given.length === expected.length && timingSafeEqual(given, expected);
}

With Express, keep the raw body for the check:

app.post('/trustink/webhook', express.raw({type: 'application/json'}), (req, res) => {
const rawBody = req.body.toString('utf8');
if (!verify(process.env.TRUSTINK_WEBHOOK_SECRET, rawBody, req.get('Trustink-Signature') ?? '')) {
return res.sendStatus(400);
}
const event = JSON.parse(rawBody);
// … handle event.type, dropping ids you have seen
res.sendStatus(204);
});

Use it to check your implementation. With the clock set within 300 seconds of t, it verifies; change one character of the body or the secret and it must not.

Signing secret whsec-trustink-docs-test-vector-2026
t 1790178000
Header Trustink-Signature: t=1790178000,v1=388855130144b108ad958f6a1d73a518e24d1a449f0e602ef96b3b2715f848a7
Raw body (one line, no trailing newline)
{"id":"7b1e4c2a-5d3f-4e8a-9c21-5d6f7a8b9c0d","type":"case-completed","occurredAt":"2026-09-23T15:40:00Z","tenantId":"d47b9463-2c1e-4f7a-8b5d-6e9f0a1b2c3d","caseId":"49d4ae62-8e3b-4d1f-a6c7-0b2e4f6a8c1e","status":"DONE","data":{"orderId":4711},"signedDocument":{"url":"https://example.com/signed.pdf"},"evidenceDocument":{"url":"https://example.com/evidence.pdf"}}

The signed message is t, a dot and the raw body; its HMAC-SHA256 with the secret, in lowercase hex, is 388855130144b108ad958f6a1d73a518e24d1a449f0e602ef96b3b2715f848a7. The samples on this page are run against this vector in the site’s test suite.

Your endpoint has 10 seconds to answer; redirects are not followed.

Answer Result
2xx delivered
408, 429, 5xx, no answer, connection error retried every 90 seconds, at most 8 attempts
any other status (e.g. 400, 401, 404, 3xx) failed, not retried

Events that fail all 8 attempts are recorded as dead. Every attempt is kept for 90 days with its status code, duration and error.