Reference for theAPI Full
A precise technical reference for every endpoint and object in the Wthaiq API: authentication, errors, pagination, Idempotency, rate limits, signature requests, signers, documents, templates, identity verification, verification, events and Webhooks. Ready examples in cURL and Node for every call.
https://wthaiq.com/api/v1
2026-07-01
Authentication Bearer sk_...
Response application/json
curl -X POST https://wthaiq.com/api/v1/signature_requests \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f3c9c2e-..." \
-d '{
"title": "Employment Contract — Ahmed M.",
"source": { "type": "template", "template_id": "tpl_employment" },
"legal_level": "aes",
"ordered": true,
"signers": [
{ "name": "Ahmed Mohamed", "email": "ahmed@example.com",
"method": "draw", "require_identity": true }
]
}'
Overview
The Wthaiq API is a REST API organized around resources; it uses predictable HTTP paths, encodes errors with standard HTTP codes, and sends and receives payloads of application/json (except for file uploads, which use multipart/form-data). All requests go over HTTPS only.
Timestamps are integers in Unix epoch seconds. Every resource carries a unique ID prefixed with its type, which makes tracing objects in logs straightforward:
| Prefix | Resource | Example |
|---|---|---|
| sr_ | signature_request | sr_3n8Kd2Qa1V |
| sgr_ | signer | sgr_9fA2 |
| doc_ | document | doc_7Yq |
| tpl_ | template | tpl_employment |
| idv_ | identity_verification | idv_5k |
| evt_ | event | evt_2M |
| we_ | webhook_endpoint | we_1a |
| key_ | api_key | key_88 |
https://wthaiq.com/api/v1. The version is pinned by date through the header Wthaiq-Version, and the changes are dated in Changelog.wthaiq library; its install command and the other libraries' examples are on the official libraries page.Authentication
Requests are authenticated with a secret key in the header Authorization using the HTTP Bearer scheme. Your keys carry full privileges; keep them on the server only, and never place them in client-side code, public repositories or mobile applications.
| Key | The format | Description |
|---|---|---|
| The secret key | sk_... | Full permissions, for server-side use only. A live key sends real messages, issues legal certificates and its sends are billed, while a test key sk_test_ does none of that. See the section “Test keys and live keys”. |
| The publishable key | pk_... | Safe to expose in the browser, locked to your domains, and limited to a fixed set of paths. |
| The Webhook secret | whsec_... | Used to verify the signature of Webhook messages (HMAC-SHA256). |
curl https://wthaiq.com/api/v1/signature_requests \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
401 and an error type authentication_error. Manage your keys and delete any leaked key from the developer console immediately.Versioning
The version is pinned by date. Send the header Wthaiq-Version with the date of the version you built your integration against, so API behavior stays fixed even if we ship later changes. If you omit the header, the latest version pinned to your account is used.
curl https://wthaiq.com/api/v1/templates \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
Errors
Wthaiq uses conventional HTTP response codes: the range 2xx success, and the range 4xx indicates an error in the input, and the range 5xx indicates an error on our servers. Every error is returned inside an envelope error consistent, carrying a type, a code and a message, and usually the name of the offending parameter and the request ID.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "The signers parameter is required.",
"param": "signers",
"request_id": "req_9f2"
}
}
| Type | HTTP code | Meaning |
|---|---|---|
| authentication_error | 401 | Missing or invalid key. |
| invalid_request_error | 400 / 404 | A missing or invalid parameter, or a resource that does not exist. |
| quota_error | 402 | Quota exceeded, or a higher plan is required. |
| permission_error | 403 | Your plan or account status does not allow the operation: company verification is required for a live request, the feature is not in your plan, or the IP address is not allowed for the key. |
| rate_limit_error | 429 | More requests than allowed. |
| idempotency_error | 409 | Idempotency key conflict. |
| api_error | 5xx | An internal error on the Wthaiq servers. |
200 Success · 201 Created · 403 Forbidden · the range 5xx Server error.| The field | With code | Meaning |
|---|---|---|
| upgrade_url | plan_required · plan_expired · plan_feature_unavailable | Link to the plans page in the developer portal, to choose, renew or upgrade a plan. |
| verify_url | kyb_required | Link to the Company verification page in the developer portal. |
| feature · required_plan | plan_feature_unavailable | The requested feature, and the lowest plan that includes it. |
| max_signers | too_many_signers | The maximum number of signers in a single live request, which is 5. |
Accept-Language when its value starts with en, the parameter ?lang=en, or the path /en/api/v1/… which behaves like /api/v1/… in every respect. The code field never changes with the language, so build your integration logic on it, not on the message text.Pagination
All list endpoints use cursor pagination, controlled by two query parameters, and every list returns a uniform envelope with object: "list".
| Parameter | Type | Description |
|---|---|---|
| limit | integer | The number of items per page, from 1 to 100 (default 20). |
| starting_after | string | The ID of the item after which pagination starts (the next page). |
{
"object": "list",
"data": [
{ "id": "sr_3n8Kd2Qa1V", "object": "signature_request", "status": "sent" }
],
"has_more": true,
"next_cursor": "sr_2m7Xa9"
}
next_cursor in starting_after. Stop paginating once has_more is false.Idempotency
to make retries safe on requests POST, send the header Idempotency-Key with a unique UUID for each logical operation. If the same request arrives twice (because of a dropped network connection, for example), we return the original response instead of creating a duplicate resource. Keys are retained for 24 hours.
curl -X POST https://wthaiq.com/api/v1/signature_requests \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f3c9c2e-8b1a-4c7d-9e21-2f0a6b3d1c44" \
-d '{ "title": "Employment Contract — Ahmed M." }'
409 and an error type idempotency_error.409 with the code idempotency_in_progress with the header Retry-Afteris returned; retry in a moment to receive the original response without a second execution.Rate limits
The limit is 100 requests per 10 seconds per account, a single limit shared by test and live mode. Every response carries headers showing your remaining quota, and when you exceed it the status returned is 429 with the header Retry-After with the number of seconds to wait before retrying.
| The header | Description |
|---|---|
| X-RateLimit-Limit | The maximum number of requests in the current window. |
| X-RateLimit-Remaining | The number of requests remaining in the current window. |
| X-RateLimit-Reset | The Unix timestamp at which the window resets. |
| Retry-After | Appears with status 429: the number of seconds to wait before retrying. |
# HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1754000010
Retry-After: 4
Retry-After.Request IDs
Every response carries the header Wthaiq-Request-Id with a value prefixed byreq_, and the same ID appears inside the field request_id on any error. Store this ID in your logs; it is what we ask for when you contact support, so we can trace any request precisely.
# HTTP/1.1 200 OK
Content-Type: application/json
Wthaiq-Request-Id: req_9f2a1c
Test keys and live keys
Every key comes in two forms: live sk_ / pk_ and test sk_test_ / pk_test_. You choose the test form when creating the key with the “Test key” option under: Developer console → API keys. The field livemode in every object and event shows which of the two keys created it.
| Aspect | Live key | Test key |
|---|---|---|
| Billing | Every request you send is charged to your balance | Nothing is charged |
| Messages | Invitations, reminders and copies are actually sent | The invitation and reminders arrive by email marked “Test”, and no copies are sent; WhatsApp messages are not sent but logged in the request events as “Would have been sent via WhatsApp to …” |
| Limits | As per your plan, and so are the features | Every feature except the custom domain; 20 requests and 50 emails a day per account, 5 signers per request and 10 recipients per bulk batch |
| Signers | Any email or number | Only your account’s email or number, or a team member’s email or number |
| Legal level | ses · aes · qes | The requested level itself; identity verification is a simulation in which the signer chooses “Approve” or “Decline” with no provider session, and the whole request has no legal effect |
| Evidence record | Sealed on completion, emitting the events document.sealed and verification.created | Not sealed and no timestamp is requested, and neither event is emitted; GET /signature_requests/{id}/evidence returns an unsealed test record with the test field, and the final file carries the watermark “TEST — no legal effect” |
| Event notifications | Delivered to live webhook endpoints | Delivered to webhook endpoints created with a test key via POST /v1/webhook_endpoints only; endpoints added from the developer console are live |
The secret and publishable keys also differ in Access scope:
| Aspect | sk_ (secret) | pk_ (publishable) |
|---|---|---|
| Where it is used | Server only — never exposed | Safe to expose in the browser |
| Permissions | full across all resources | Limited to specific paths (templates, signature requests, signers, flows) for reading, with the creation of signature requests only |
| Domain locking | There is no | Locked to the domains allowed_origins that you define — This reduces risk but is not authentication: the Origin header is issued by the browser, and any script outside a browser can forge it. The real protections are the key's daily spend cap and rotating it the moment you suspect exposure. |
| Daily cap | There is no | Each key has a daily cap on create and send operations, regardless of how many IP addresses are used. Once reached, status 429 is returned with the code daily_cap_reached until the start of the next day (UTC), and a notification reaches your dashboard at 80% of the cap and when it is reached. |
sk_ on your server only, and pk_ locked to your domain for any code running in the browser.Metadata
Most creatable objects accept the field metadata: a string key/value map in which you store your own internal references (an order number, a user ID in your system, a campaign tag). We do not use these values internally, and they are returned unchanged in every response and in Webhook messages.
| Constraint | Limit |
|---|---|
| Number of keys | Up to 50 keys per object. |
| Key length | Up to 40 characters. |
| Value length | Up to 500 characters. |
{
"metadata": {
"order_id": "A-1024",
"crm_contact": "cust_58231",
"campaign": "q3-onboarding"
}
}
Signature Requests
The signature request is the central object: it represents a document (from a template or an uploaded file) sent to one or more signers at a defined legal (assurance) level, with full tracking of each signer's status through to completion, at which point the request is sealed with an authenticated, independently verifiable evidence record.
/v1/signature_requestsCreate a new signature request — as a draft, or sent to the signers immediately.
| The field | Type | Required | Description |
|---|---|---|---|
| title | string | Required | The request title shown to the signers. |
| source | object | Required | The source of the document; the value of type either template with template_id, or document with document_id. The document source (an uploaded PDF) is not available yet (coming soon) and is rejected with a 400 error. |
| legal_level | string | Required | Legal level: ses or aes or qes. |
| signers | array | Required | The list of signers (see the fields below). |
| format | string | Optional | The requested assurance level; it is recorded and returned on the object: pades-b / pades-t / pades-lt / pades-lta (default pades-lt). The API path does not currently issue a PAdES signature embedded inside the PDF file; the cryptographic evidence is the sealed evidence record (an Ed25519 signature that any party can verify with the public key at /trust, plus an RFC 3161 timestamp). |
| ordered | boolean | Optional | Sequential signing in the order of the signers in the array signers (default false). |
| require_identity | boolean | Optional | Require identity verification before signing, at the request level. |
| cc | array | Optional | Copy recipients — they receive the signed contract on completion without signing (up to 20). Each item { name, email }. |
| reminders | object | Optional | { "enabled": true } sends up to three automatic reminders to the signer whose turn has come, five days, then three days, then one day before the deadline, for sent live requests only. interval_hours and max are stored and returned as sent, without being used. A request expires 7 days after it is sent. |
| send | boolean | Optional | Send the request as soon as it is created (default true). The value false creates a draft whose signing link does not work until you send it via POST /v1/signature_requests/{id}/send. |
| metadata | object | Optional | Key/value metadata. |
| signers[].name | string | Required | The signer's name. |
| signers[].email | string | Optional* | The signer’s email address. * Required unless you send phone. |
| signers[].phone | string | Optional* | The signer’s WhatsApp number (normalised to international format automatically, for example 201012345678). Sent together with an email, the invitation goes out through both channels. Sending it on its own is accepted only while verification codes can be delivered over WhatsApp; otherwise the request is rejected with 400 — so that no signer is left facing a code that cannot reach them. |
| signers[].method | string | Optional | draw (a drawn signature + OTP) or token (a QES certificate on a hardware token). |
| signers[].require_identity | boolean | Optional | Require identity verification for this specific signer (AES). |
| signers[].fields | object | Optional | The pre-filled template field values. |
curl -X POST https://wthaiq.com/api/v1/signature_requests \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f3c9c2e-..." \
-d '{
"title": "Employment Contract — Ahmed M.",
"source": { "type": "template", "template_id": "tpl_employment" },
"legal_level": "aes",
"ordered": true,
"signers": [
{ "name": "Ahmed Mohamed", "email": "ahmed@example.com", "type": "individual",
"method": "draw", "require_identity": true,
"fields": { "job_title": "Software Engineer", "salary": "25000" } }
],
"reminders": { "enabled": true, "interval_hours": 48, "max": 3 },
"metadata": { "order_id": "A-1024" }
}'
import Wthaiq from 'wthaiq';
const wt = new Wthaiq('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
const sr = await wt.signatureRequests.create({
title: 'Employment Contract — Ahmed M.',
source: { type: 'template', template_id: 'tpl_employment' },
legal_level: 'aes',
ordered: true,
signers: [
{ name: 'Ahmed Mohamed', email: 'ahmed@example.com', method: 'draw', require_identity: true }
]
});
{
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"livemode": true,
"status": "sent",
"title": "عقد عمل — أحمد م.",
"legal_level": "aes",
"format": "pades-lt",
"source": { "type": "template", "template_id": "tpl_employment" },
"signers": [
{
"id": "sgr_9fA2",
"object": "signer",
"name": "أحمد محمد",
"email": "ahmed@example.com",
"type": "individual",
"method": "draw",
"require_identity": true,
"order": 1,
"status": "sent",
"signing_url": "https://wthaiq.com/sign.php?token=uZ8..",
"viewed_at": null,
"signed_at": null,
"identity": { "status": "required", "provider": "external", "level": "aes" },
"fields": { "job_title": "مهندس برمجيات", "start_date": "2026-08-01" }
}
],
"ordered": true,
"require_identity": true,
"reference": null,
"reminders": { "enabled": true, "interval_hours": 48, "max": 3 },
"expires_at": 1755000000,
"completed_at": null,
"download_url": null,
"metadata": { "order_id": "A-1024" },
"created_at": 1754000000
}
Bulk send: the same contract to several signers at once — creates A separate signature request for each signer (up to 200). The balance is checked for the entire batch before it starts. Every recipient is also validated before any request is created; if one is invalid, the whole batch is rejected with a 400 error that names it in the param field, and nothing is created.
| Parameter | Type | Required | Description |
|---|---|---|---|
| source | object | Required | The contract source — such as { "type": "template", "template_id": "tpl_..." }. |
| recipients | array | Required | The list of signers; each item { name, email }. |
| fields | object | Optional | The shared field values for all the contracts. |
| legal_level | string | Optional | ses or aes or qes. |
| cc | array | Optional | Copy recipients for each contract. |
curl -X POST https://wthaiq.com/api/v1/signature_requests/bulk \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-d '{
"source": { "type": "template", "template_id": "tpl_265" },
"recipients": [
{ "name": "Mohamed Salem", "email": "m1@example.com" },
{ "name": "Sara Ahmed", "email": "s2@example.com" }
],
"fields": { "amount": "5000" },
"legal_level": "ses"
}'{
"object": "bulk_send",
"created": 2,
"failed": 0,
"requests": [
{ "signature_request": "sr_3n8Kd2Qa1V", "email": "m1@example.com", "status": "sent" },
{ "signature_request": "sr_9Kx2Ld7Pq3", "email": "s2@example.com", "status": "sent" }
],
"errors": []
}
List signature requests with cursor pagination, with optional filtering by status and date (created_after / created_before).
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | Optional | From 1 to 100 (default 20). |
| starting_after | string | Optional | The cursor for the next page. |
| status | string | Optional | Filter by status: draft, sent, completed ... |
curl "https://wthaiq.com/api/v1/signature_requests?limit=3&status=sent" \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
const list = await wt.signatureRequests.list({ limit: 3, status: 'sent' });
{
"object": "list",
"data": [
{
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"livemode": true,
"status": "sent",
"title": "Employment Contract — Ahmed M.",
"legal_level": "aes",
"format": "pades-lt",
"created_at": 1754000000
}
],
"has_more": true,
"next_cursor": "sr_2m7Xa9"
}
Retrieve a signature request by its ID, with its list of signers and their status.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signature request ID (sr_). |
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
const sr = await wt.signatureRequests.retrieve('sr_3n8Kd2Qa1V');
{
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"livemode": true,
"status": "partially_signed",
"title": "Employment Contract — Ahmed M.",
"legal_level": "aes",
"format": "pades-lt",
"source": { "type": "template", "template_id": "tpl_employment" },
"ordered": true,
"require_identity": true,
"reference": null,
"expires_at": 1755000000,
"completed_at": null,
"download_url": null,
"metadata": { "order_id": "A-1024" },
"created_at": 1754000000
}
Send a request from draft status and push it to the signers.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signature request ID. |
curl -X POST https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/send \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"status": "sent",
"created_at": 1754000000
}
Cancel a signature request that has not yet completed; its status becomes canceled. A request that is already completed or canceled is rejected with status 409 and the code request_not_cancelable. Once a request has been sent, its cost is not refunded, even if it is canceled before anyone signs it.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signature request ID. |
curl -X POST https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/cancel \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"status": "canceled",
"completed_at": null,
"created_at": 1754000000
}
Trigger an immediate reminder to the pending signers on this request. A draft or canceled request is rejected with status 400.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signature request ID. |
curl -X POST https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/reminders \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "reminder",
"signature_request": "sr_3n8Kd2Qa1V",
"reminders_sent": 1
}
Returns the signed contract data as JSON once the request is complete: pdf_url is the link to the final PDF (the frozen contract text with the verification certificate, its reference and QR code), pdf_status is its state: ready or pending or unavailable, and pdf_sha256 is its fingerprint, final_document_url is the final document page, and reference is the verification reference. The same file is downloaded via GET /v1/signature_requests/{id}/download/pdf with a secret key, and in live mode its link is e-mailed to every signer and to the sender on completion. The independent cryptographic proof is the evidence record via GET /v1/signature_requests/{id}/evidence.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The ID of the completed signature request. |
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/download \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "download",
"signature_request": "sr_3n8Kd2Qa1V",
"pdf_url": "https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/download/pdf",
"pdf_status": "ready",
"pdf_sha256": "455e49ec8a9975bb6ba5500fffb0d3af1a364ab2a1b1c1335020b17aef8316ef",
"pdf_bytes": 100993,
"pdf_generated_at": 1791021338,
"final_document_url": "https://wthaiq.com/final/9c1e..",
"reference": "WTQ-1A2B3C4D5E6F",
"format": "pades-lt",
"legal_level": "aes"
}
400 and an error type invalid_request_error.Completion certificate data with the audit trail, as JSON.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signature request ID. |
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/certificate \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "certificate",
"signature_request": "sr_3n8Kd2Qa1V",
"status": "completed",
"reference": "WTQ-1A2B3C4D5E6F",
"legal_level": "aes",
"format": "pades-lt",
"completed_at": 1754500000,
"signers": [
{ "name": "Ahmed Mohamed", "email": "ahmed@example.com", "phone": null, "status": "signed", "signed_at": 1754500000 }
],
"audit_trail": [
{ "event": "sent", "actor": "owner", "ip": "203.0.113.7", "at": 1754000000 },
{ "event": "signed", "actor": "signer", "ip": "198.51.100.4", "at": 1754500000 }
],
"verify_url": "https://wthaiq.com/verify?c=WTQ-1A2B3C4D5E6F"
}
The sealed evidence record of a completed request, for independent verification: the record’s canonical text record_raw with the Ed25519 signature over it, the public key and the RFC 3161 timestamp. Requires a secret key sk_, and returns 404 until the record is sealed; test requests are never sealed.
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/evidence \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "evidence",
"signature_request": "sr_3n8Kd2Qa1V",
"format": "wthaiq-evidence-v1",
"verify_ref": "WTQ-1A2B3C4D5E6F",
"record_raw": "{\"document\":{..}}",
"seal": {
"signature_alg": "ed25519",
"signature": "b64..",
"public_key": "b64..",
"trust_url": "https://wthaiq.com/trust",
"timestamp_token": "b64.."
}
}
The chronological audit trail for this request — every event from opening to signing.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signature request ID. |
| limit | integer · query | Optional | From 1 to 100 (default 20). |
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/events \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "list",
"data": [
{
"id": "evt_2M",
"object": "event",
"type": "signer.signed",
"signature_request": "sr_3n8Kd2Qa1V",
"signer": "sgr_9fA2",
"actor": "signer",
"ip": "197.44.x.x",
"user_agent": "..",
"created_at": 1754500000
}
],
"has_more": false,
"next_cursor": null
}
Signers
A signer represents a party to a signature request, with its own status, signing method and identity verification result. It is created implicitly when the request is created, and it can be retrieved or given an embedded signing session.
/v1/signersRetrieve a signer by ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signer ID (sgr_). |
curl https://wthaiq.com/api/v1/signers/sgr_9fA2 \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"id": "sgr_9fA2",
"object": "signer",
"name": "أحمد محمد",
"email": "ahmed@example.com",
"type": "individual",
"method": "draw",
"require_identity": true,
"order": 1,
"status": "identity_verified",
"signing_url": "https://wthaiq.com/sign.php?token=uZ8..",
"viewed_at": 1754000100,
"signed_at": null,
"identity": { "status": "approved", "provider": "external", "level": "aes" },
"fields": { "job_title": "مهندس برمجيات", "start_date": "2026-08-01" }
}
Creates an embedded signing link valid for 30 minutes, to show the signing page inside your app under your brand (white-label). It cannot be created for a draft or canceled request.
| The field | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The signer ID. |
| return_url | string | Optional | A full https URL on a domain accepted in your account, to which the signer returns after signing; available on the Business plan and above. The session lifetime is fixed at 30 minutes. |
| mode | string | Optional | iframe (default) to embed in a frame on a page under a domain accepted in your account, or redirect to send the browser to the signing page and then back to success_url or cancel_url. Do not send return_url with mode redirect. |
| success_url | string | Required with redirect | An https URL on a domain accepted on your account (up to 500 characters, no username, no #). The signer is redirected to it after signing, with session_id (the request ID), signer_id and status=completed. {signature_request} and {signer} in the URL are replaced with the request and signer IDs. |
| cancel_url | string | Optional | Like success_url; the signer is redirected to it after declining, with status=declined. If omitted, the signer stays on the signing page after declining. Rely on the signed webhook for the outcome, not on the redirect. |
curl -X POST https://wthaiq.com/api/v1/signers/sgr_9fA2/signing_session \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-H "Content-Type: application/json" \
-d '{ "return_url": "https://app.acme.com/done" }'
{
"object": "signing_session",
"signer": "sgr_9fA2",
"signing_url": "https://wthaiq.com/embed/sign/9Qa1V..",
"expires_at": 1754001800
}
Documents
A document is an uploaded PDF saved in your account with its digital fingerprint (SHA-256), page count and size. Using it as a signature-request source through the API is not available yet (coming soon).
/v1/documentsUpload a PDF file via multipart/form-data and saves it in your account; it counts toward your plan’s contract limit. It cannot yet be used as a signature-request source; a request that references it is rejected with a 400 error until this becomes available (coming soon).
| The field | Type | Required | Description |
|---|---|---|---|
| file | file | Required | The PDF file (multipart form field). |
| kind | string | Optional | Document type: pdf (default) or contract. |
| field_map | string (JSON) | Optional | A coordinate field map by which the values are printed in their positions on the pages. The coordinates are relative (0..1) measured from the top left of the page. |
{ "key", "label", "type", "page", "x", "y", "w", "h", "required" }. The map is saved with the document and returned in the field fields, but its values are not printed on the file yet (coming soon). curl -X POST https://wthaiq.com/api/v1/documents \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-F "file=@agreement.pdf" \
-F "kind=pdf" \
-F 'field_map=[
{"key":"signer_name","label":"Signer name","type":"text",
"page":1,"x":0.12,"y":0.34,"w":0.30,"h":0.04,"required":true},
{"key":"contract_date","label":"Date","type":"date",
"page":1,"x":0.62,"y":0.34,"w":0.22,"h":0.04}
]'
{
"id": "doc_7Yq",
"object": "document",
"kind": "pdf",
"filename": "agreement.pdf",
"pages": 4,
"bytes": 183221,
"sha256": "a3f1..",
"created_at": 1754000000
}
Templates
Templates are the contracts saved in your account, with their fillable fields. Signature requests refer to them through source.template_id.
List the templates saved in your account.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer · query | Optional | From 1 to 100 (default 20). |
| category | string · query | Optional | Filter by category, such as hr. |
| starting_after | string · query | Optional | The cursor for the next page. |
curl "https://wthaiq.com/api/v1/templates?category=hr" \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
const tpls = await wt.templates.list({ category: 'hr' });
{
"object": "list",
"data": [
{
"id": "tpl_employment",
"object": "template",
"name": "Employment Contract",
"category": "hr",
"languages": ["ar"],
"created_at": 1754000000
}
],
"has_more": false,
"next_cursor": null
}
Retrieve a template together with the definition of its fillable fields.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The template ID (tpl_). |
curl https://wthaiq.com/api/v1/templates/tpl_employment \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
const tpl = await wt.templates.retrieve('tpl_employment');
{
"id": "tpl_employment",
"object": "template",
"name": "Employment Contract",
"category": "hr",
"languages": ["ar"],
"fields": [
{ "key": "job_title", "label": "Job title", "type": "text", "required": true },
{ "key": "salary", "label": "Salary", "type": "number", "required": true }
],
"created_at": 1754000000
}
Identity Verifications
Identity verification ties the signer's signature to a real person verified through a specialized identity-verification provider (an official document and a live face match), and it is a requirement of the advanced level (AES).
/v1/identity_verificationsRetrieve an identity verification by its ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The identity verification ID (idv_). |
curl https://wthaiq.com/api/v1/identity_verifications/idv_5k \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"id": "idv_5k",
"object": "identity_verification",
"provider": "external",
"signer": "sgr_9fA2",
"status": "approved",
"level": "aes",
"verification_url": "https://verify.wthaiq.com/u/..",
"created_at": 1754000000
}
Verifications
Public verification of a signed document's integrity through its public reference (WTQ- / WTH-), without exposing the parties' full details. It compares the document's current hash with the sealed one.
Public verification of a document through its reference.
| Parameter | Type | Required | Description |
|---|---|---|---|
| reference | string · path | Required | The public reference, such as WTQ-1A2B3C4D5E6F. |
curl https://wthaiq.com/api/v1/verifications/WTQ-1A2B3C4D5E6F \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "verification",
"reference": "WTQ-1A2B3C4D5E6F",
"found": true,
"status": "completed",
"integrity": "intact",
"document": {
"title": "Employment Contract",
"sha256": "a3f1..",
"hash_alg": "hmac-sha256",
"format": "platform-evidence",
"legal_level": "aes"
},
"signed_at": 1754500000,
"parties": [
{ "name": "A**** M****", "role": "signer" },
{ "name": "W**** C****", "role": "owner" }
],
"qr": "https://wthaiq.com/verify?c=WTQ-1A2B3C4D5E6F"
}
Events
Every event is an immutable object that represents an entry in the audit trail and reflects a change in a resource. These same events are what Webhook messages deliver, and they can be listed and retrieved later.
/v1/eventsList the account events, with optional filtering by type.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer · query | Optional | From 1 to 100 (default 20). |
| type | string · query | Optional | Filter by event type, such as signer.signed. |
| starting_after | string · query | Optional | The cursor for the next page. |
curl "https://wthaiq.com/api/v1/events?limit=10&type=signer.signed" \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "list",
"data": [
{
"id": "evt_2M",
"object": "event",
"type": "signer.signed",
"signature_request": "sr_3n8Kd2Qa1V",
"signer": "sgr_9fA2",
"actor": "signer",
"ip": "197.44.x.x",
"user_agent": "..",
"created_at": 1754500000
}
],
"has_more": true,
"next_cursor": "evt_1L"
}
Retrieve a single event by its ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The event ID (evt_). |
curl https://wthaiq.com/api/v1/events/evt_2M \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"id": "evt_2M",
"object": "event",
"type": "signer.signed",
"signature_request": "sr_3n8Kd2Qa1V",
"signer": "sgr_9fA2",
"actor": "signer",
"ip": "197.44.x.x",
"user_agent": "..",
"created_at": 1754500000
}
Redelivers the webhook of a past event with the same payload and the same ID, signed with the endpoint's current secret. A new delivery is created and attempted immediately, and follows the usual retry schedule if your server does not respond with 2xx. Limit: five per event and thirty per account every ten minutes, then 429.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The event ID (evt_). |
| endpoint | string | Optional | ID of an enabled endpoint (we_) to redeliver to it only. Without it, the event is redelivered to every enabled endpoint subscribed to its type. |
curl -X POST https://wthaiq.com/api/v1/events/evt_2M/redeliver \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-H "Content-Type: application/json" \
-d '{ "endpoint": "we_8Hk2" }'
{
"object": "event_redelivery",
"event": "evt_2M",
"deliveries": [
{ "endpoint": "we_8Hk2", "status": "succeeded", "response_code": 200, "error": null }
]
}
Webhook Endpoints
A Webhook endpoint registers a URL that receives real-time notifications of the events you subscribe to. See the guide Webhooks for how to verify the notification signature and how retries work.
/v1/webhook_endpointsCreate a new Webhook endpoint. The secret value is returned whsec_ once, at creation.
| The field | Type | Required | Description |
|---|---|---|---|
| url | string | Required | The HTTPS URL that receives POST messages. |
| enabled_events | array | Required | The event types subscribed to, such as signature_request.completed. |
curl -X POST https://wthaiq.com/api/v1/webhook_endpoints \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.acme.com/hooks/wthaiq",
"enabled_events": ["signature_request.completed", "signer.signed"]
}'
const we = await wt.webhookEndpoints.create({
url: 'https://api.acme.com/hooks/wthaiq',
enabled_events: ['signature_request.completed', 'signer.signed']
});
{
"id": "we_1a",
"object": "webhook_endpoint",
"url": "https://api.acme.com/hooks/wthaiq",
"enabled_events": ["signature_request.completed", "signer.signed"],
"status": "enabled",
"secret": "whsec_..",
"created_at": 1754000000
}
List the registered Webhook endpoints.
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer · query | Optional | From 1 to 100 (default 20). |
curl https://wthaiq.com/api/v1/webhook_endpoints \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"object": "list",
"data": [
{
"id": "we_1a",
"object": "webhook_endpoint",
"url": "https://api.acme.com/hooks/wthaiq",
"enabled_events": ["signature_request.completed", "signer.signed"],
"status": "enabled",
"created_at": 1754000000
}
],
"has_more": false,
"next_cursor": null
}
Retrieve a Webhook endpoint by its ID.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The Webhook endpoint ID (we_). |
curl https://wthaiq.com/api/v1/webhook_endpoints/we_1a \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"id": "we_1a",
"object": "webhook_endpoint",
"url": "https://api.acme.com/hooks/wthaiq",
"enabled_events": ["signature_request.completed", "signer.signed"],
"status": "enabled",
"secret": "whsec_..",
"created_at": 1754000000
}
Delete a Webhook endpoint; events stop being sent to it immediately.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string · path | Required | The Webhook endpoint ID. |
curl -X DELETE https://wthaiq.com/api/v1/webhook_endpoints/we_1a \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
{
"id": "we_1a",
"object": "webhook_endpoint",
"deleted": true
}
The central object of a signature request.
{
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"livemode": true,
"status": "sent",
"title": "Employment Contract — Ahmed M.",
"legal_level": "aes",
"format": "pades-lt",
"source": { "type": "template", "template_id": "tpl_employment" },
"signers": [ ],
"ordered": true,
"require_identity": true,
"reference": null,
"reminders": { "enabled": true, "interval_hours": 48, "max": 3 },
"expires_at": 1755000000,
"completed_at": null,
"download_url": null,
"metadata": { "order_id": "A-1024" },
"created_at": 1754000000
}
| The field | Type | Description |
|---|---|---|
| id | string | The request ID. |
| status | string | draft · sent · partially_signed · completed · declined · expired · canceled. |
| legal_level | string | ses · aes · qes. |
| format | string | The requested assurance level recorded on the object (pades-b / t / lt / lta). The API path does not issue a PAdES signature embedded inside the PDF; the proof is the sealed evidence record, which is independently verifiable. |
| source | object | The source of the document (a template or an uploaded document). |
| signers | array | The signer objects. |
| ordered | boolean | Sequential signing. |
| require_identity | boolean | Identity verification before signing (AES). |
| reference | string · null | The public verification reference (WTQ-XXXXXXXXXXXX), issued on completion. |
| download_url | string · null | The signed PDF URL, which appears on completion. |
| created_at | integer | The Unix timestamp of creation. |
A signing party within a request.
{
"id": "sgr_9fA2",
"object": "signer",
"name": "أحمد محمد",
"email": "ahmed@example.com",
"type": "individual",
"method": "draw",
"require_identity": true,
"order": 1,
"status": "identity_verified",
"signing_url": "https://wthaiq.com/sign.php?token=uZ8..",
"viewed_at": 1754000100,
"signed_at": null,
"identity": { "status": "approved", "provider": "external", "level": "aes" },
"fields": { "job_title": "مهندس برمجيات", "start_date": "2026-08-01" }
}
| The field | Type | Description |
|---|---|---|
| type | string | individual or company. |
| method | string | draw (drawn signature + email OTP) or token (a QES certificate on a hardware token). |
| status | string | pending · sent · viewed · otp_verified · identity_verified · signed · declined. |
| signing_url | string | The URL of the hosted or embeddable signing page. |
| identity | object | The identity verification result, when enabled via require_identity. |
| identity.status | string | none · required · in_review · resubmission_requested · declined · approved. The value changes with the signer.identity_* events. |
| identity.verification_id | string | ID of the latest identity verification for the signer (idv_), or null before one starts. |
| identity.reason_code | string | A general classified reason on decline, with no personal data: reviewer_decision · face_mismatch · face_unclear · document_expired · document_unreadable · document_not_original · document_not_supported · not_accepted. |
| identity.resubmit_steps | array | The steps to redo when resubmission is requested: document · liveness. |
| identity.updated_at | integer | Time of the last change in the identity confirmation status. |
| fields | object | The pre-filled template field values. |
An uploaded PDF file.
{
"id": "doc_7Yq",
"object": "document",
"kind": "pdf",
"filename": "agreement.pdf",
"pages": 4,
"bytes": 183221,
"sha256": "a3f1..",
"created_at": 1754000000
}
| The field | Type | Description |
|---|---|---|
| kind | string | pdf or contract. |
| pages | integer | The page count. |
| bytes | integer | The file size in bytes. |
| sha256 | string | The document's SHA-256 hash. |
A ready-made contract with fillable fields.
{
"id": "tpl_employment",
"object": "template",
"name": "Employment Contract",
"category": "hr",
"languages": ["ar"],
"fields": [
{ "key": "job_title", "label": "Job title", "type": "text", "required": true },
{ "key": "salary", "label": "Salary", "type": "number", "required": true }
],
"created_at": 1754000000
}
| The field | Type | Description |
|---|---|---|
| name | string | The template name. |
| category | string | The template category, such as hr. |
| languages | array | The languages available for the template. |
| fields | array | Field definitions: key, label, type, required. |
The result of a signer's identity verification through a specialized identity-verification provider.
{
"id": "idv_5k",
"object": "identity_verification",
"provider": "external",
"signer": "sgr_9fA2",
"status": "approved",
"level": "aes",
"verification_url": "https://verify.wthaiq.com/u/..",
"created_at": 1754000000
}
| The field | Type | Description |
|---|---|---|
| provider | string | The verification provider (external). |
| signer | string | The associated signer ID. |
| status | string | pending · in_review · resubmission_requested · approved · declined · expired. |
| level | string | The target legal level. |
The result of a public check of a document's integrity by its reference.
{
"object": "verification",
"reference": "WTQ-1A2B3C4D5E6F",
"found": true,
"status": "completed",
"integrity": "intact",
"document": {
"title": "Employment Contract",
"sha256": "a3f1..",
"hash_alg": "hmac-sha256",
"format": "platform-evidence",
"legal_level": "aes"
},
"signed_at": 1754500000,
"parties": [
{ "name": "A**** M****", "role": "signer" },
{ "name": "W**** C****", "role": "owner" }
],
"qr": "https://wthaiq.com/verify?c=WTQ-1A2B3C4D5E6F"
}
| The field | Type | Description |
|---|---|---|
| reference | string | The public reference (WTQ-/WTH-). |
| found | boolean | Whether the document was found. |
| integrity | string | intact compared and matched · modified compared and differed · recorded hash on record, file not available for comparison · unknown no hash on record. |
| structure | string | Structural commitment — covers tags, attributes, formatting and images, which the text hash does not cover: match · changed · absent for a signature predating this commitment. |
| document.sha256 | string | The document fingerprint recorded at sealing; its algorithm is given in the hash_alg field. |
| document.hash_alg | string | hmac-sha256 For WTQ- references: a fingerprint of the document text keyed with a secret held by the platform, so it does not equal a hash of the downloaded PDF and cannot be recomputed by a third party; it is matched through the verification page. · sha256 For WTH- references: a hash of the final file itself, which anyone can recompute. |
| document.format | string | The PAdES signature level actually embedded in the file when the party signs with a token: pades-b · pades-t · pades-lt · pades-lta. Otherwise platform-evidence: a time-stamped evidence record, with no signature embedded in the file. |
| parties | array | Parties with partially masked names. |
| qr | string | The public verification page URL. |
An immutable entry in the audit trail.
{
"id": "evt_2M",
"object": "event",
"type": "signer.signed",
"signature_request": "sr_3n8Kd2Qa1V",
"signer": "sgr_9fA2",
"actor": "signer",
"ip": "197.44.x.x",
"user_agent": "..",
"created_at": 1754500000
}
| The field | Type | Description |
|---|---|---|
| type | string | The event type, such as signer.signed. |
| actor | string | owner · signer · system. |
| ip | string | The actor's IP address. |
| user_agent | string | The actor's user agent. |
A registered URL for receiving events.
{
"id": "we_1a",
"object": "webhook_endpoint",
"url": "https://api.acme.com/hooks/wthaiq",
"enabled_events": ["signature_request.completed", "signer.signed"],
"status": "enabled",
"secret": "whsec_..",
"created_at": 1754000000
}
| The field | Type | Description |
|---|---|---|
| url | string | The receiving HTTPS address. |
| enabled_events | array | The subscribed event types. |
| status | string | enabled or disabled. |
| secret | string | The signing secret whsec_ to verify the HMAC. |
An API access key (the full secret is not shown after creation).
{
"id": "key_88",
"object": "api_key",
"name": "Production",
"prefix": "sk_9a1b2c3d4e5f6",
"livemode": true,
"created_at": 1754000000,
"last_used_at": 1754500000
}
| The field | Type | Description |
|---|---|---|
| name | string | The key's descriptive name. |
| prefix | string | The visible prefix of the key. |
| livemode | boolean | Whether the key is live. |
| last_used_at | integer · null | Last used (Unix timestamp). |
Legal levels — quick reference
The value of legal_level determines the strength of evidence and the target assurance level. For full details, see the page Legal standing and trust.
| Level | Description | PAdES format |
|---|---|---|
| ses | Simple electronic signature: drawn signature + mandatory email OTP. | platform-evidence |
| aes | Advanced signature: SES + an identity verified through a specialized identity-verification provider (an official document and a live face match) that ties the signature to a real person. | platform-evidence |
| qes | Qualified signature: a digital certificate on a hardware token from a licensed authority (Misr for Central Clearing / MCDR) — the highest legal standing under Egyptian Electronic Signature Law No. 15 of 2004. | up to pades-lta |
| The format | What it adds |
|---|---|
| pades-b | The baseline signature. |
| pades-t | + a trusted timestamp (RFC 3161). |
| pades-lt | + DSS: certificates, OCSP and CRL for long-term preservation. |
| pades-lta | + an archival timestamp, the highest level produced by token signing. |
| platform-evidence | No PAdES signature embedded in the file: a time-stamped evidence record (Ed25519 + RFC 3161) and an audit trail. This is what drawn signatures and identity verification receive. |
GET /v1/signature_requests/{id}/evidence (with owner authentication).SubFilter adbe.pkcs7.detached · SHA256withRSA · ESS signingCertificateV2. With QES the token key never leaves the device; a local desktop agent signs (prepare signedAttributes → sign on the device → embed CMS).Ready to build on Wthaiq?
Start from the quickstart guide and issue your first signature request in minutes with a free test key, using your own email as the recipient of the first test.
Get started in five minutes