From zero to your first signed contract in minutes.
This guide walks you through five direct steps, from creating an API key to getting a legally valid signed contract and verifying it. Examples are ready to copy in cURL, Node, Python and PHP. Start with a test key: nothing is charged and no message is sent to anyone; then switch to the live key at launch.
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}
]
}'
Three requirements to get started.
Simple preparation you can finish in a minute, then move straight to your first API call.
Developer account
Create a developer account in the developer console with your work email. From there you manage your keys, your templates, your signature requests and your event logs. A developer account is separate from Wthaiq platform accounts, so if your email is already registered on the platform, use another one.
A secret API key
Create a key with the prefix sk_ from the “Integration keys” page in the developer console. It gives you full access to the API and is active from the moment it is created, so every signature request you send with a live key is real and counts against your plan allowance.
A plan for live sending
Test mode is free within a daily limit on the trial plan, while live sending requires a paid yearly developer plan and a verified company. See the Pricing page to choose the plan that suits your usage volume.
sk_test_ charges no balance, sends no invitation or reminder to anyone, accepts your account email as the signer, and goes no further than a simple signature. The live key sk_ so it really sends messages and issues binding, legally valid contracts. Every signer counts as one send from your annual plan balance, then from your wallet at the overage rate once it runs out, plus $1.50 from the wallet for each signer who requires identity verification.402 plan_required or 403 kyb_required. The test key is free within a daily limit.From the key to the signed contract — step by step.
Follow the steps in order. Each step stands on its own and comes with copy-ready examples in four languages.
Get your API key
From the developer console, open Integration keys and create a new key; choose “Test key” while you are building your integration. Keys come in two types:
| Key | Description |
|---|---|
| sk_... | A secret key for server-side use only, with full permissions. The live one actually sends messages, issues legally valid contracts and its sends are billed; the test version (sk_test_) does none of that. |
| pk_... | A publishable key, safe to expose in the browser, locked to your domains (allowed_origins), and limited to a set of paths (templates, signature requests, signers, flows). |
Authentication is performed through the header Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx with every request. Also pin the API version through the header Wthaiq-Version: 2026-07-01 so that the behavior of the API stays stable across updates.
sk_) is used on the server only. Never place it in a web or mobile app or in a public repository. If it leaks, revoke it immediately from the developer console and create a new one. See Security guide for the details.Set up the API connection
Call the API directly with cURL or any HTTP client without installing a package, or use one of the official server libraries (PHP · Node.js · Python) as the following tabs show.
# cURL needs no SDK — just set your key
export WTHAIQ_API_KEY="sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Check the connection by fetching your account's templates
curl https://wthaiq.com/api/v1/templates \
-H "Authorization: Bearer $WTHAIQ_API_KEY" \
-H "Wthaiq-Version: 2026-07-01"
npm install wthaiq
import Wthaiq from 'wthaiq';
const wt = new Wthaiq('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
pip install wthaiq
import wthaiq
wt = wthaiq.Client('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
composer require wthaiq/wthaiq-php
$wt = new \Wthaiq\Client('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
Create your first signature request
Now create a signature request from a ready-made template. In the following example we use the employment contract template (tpl_employment), and a single signer who signs by drawing their signature (method: draw) after identity verification is passed (require_identity: true), which raises the request to the advanced signature level (AES).
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"}
}'
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', 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' }
});
console.log(sr.signers[0].signing_url);
sr = wt.signature_requests.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', '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'},
)
print(sr.signers[0].signing_url)
$sr = $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', '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'],
]);
echo $sr->signers[0]->signing_url;
Response is an object signature_request with status sent, and contains the signer together with the signing link signing_url ready to open or embed:
{
"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" },
"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,
"signers": [
{
"id": "sgr_9fA2",
"object": "signer",
"name": "Ahmed Mohamed",
"email": "ahmed@example.com",
"type": "individual",
"method": "draw",
"require_identity": true,
"order": 1,
"status": "sent",
"signing_url": "https://wthaiq.com/sign.php?token=uZ8..",
"identity": { "status": "required", "provider": "external", "level": "aes" }
}
]
}
viewed and partially_signed up to completed.require_identity.WTQ-... automatically when signing completes.A shorter path — a session from a workflow: If you have set the document, the signature method and the send channel in a published workflow in the developer console, your server needs just three things: the workflow ID, the secret key and the webhook signing secret. The request names only the workflow and the signers, and the result comes back to your server in a signed webhook.
curl -X POST https://wthaiq.com/api/v1/sessions \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f3c9c2e-..." \
-d '{
"workflow_id":"wf_8f3a1c9e2b",
"vendor_data":"A-1024",
"signers":[
{"name":"Ahmed Mohamed","email":"ahmed@example.com"}
]
}'
# {"id":"sr_3n8Kd2Qa1V","object":"session","status":"sent","workflow_id":"wf_8f3a1c9e2b","workflow_version":1,"vendor_data":"A-1024","sign_url":"https://wthaiq.com/sign.php?token=uZ8..",…}
{
"id": "evt_2M",
"object": "event",
"type": "signature_request.completed",
"created_at": 1754500000,
"livemode": true,
"data": {
"object": {
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"status": "completed",
"reference": "WTQ-1A2B3C4D5E6F",
"workflow_id": "wf_8f3a1c9e2b",
"workflow_version": 1,
"vendor_data": "A-1024"
}
}
}
const crypto = require('crypto');
// rawBody: the raw body exactly as received · header: the Wthaiq-Signature header value · secret: whsec_...
function verify(rawBody, header, secret) {
const p = Object.fromEntries(header.split(',').map(x => x.split('=')));
if (!p.t || Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret)
.update(p.t + '.' + rawBody).digest('hex');
return expected.length === (p.v1 || '').length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}
// After verification succeeds: event.data.object.vendor_data is your reference, and workflow_id is your workflow
If you set a return URL in the workflow, the signer comes back to your site after signing or declining. You will find all request fields, what each request may change, and the errors on the the Signing via API page.
Track the status
There are two ways to know when the other party signs. Choose manual polling for quick experiments, and Webhooks for production.
Method one — polling: Query the request whenever you like. The status status reflects the latest state.
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Wthaiq-Version: 2026-07-01"
signature_request.completed, so you react as soon as signing completes, with no manual polling. Details of registration and signature verification are in The Webhooks page.Method two — Webhook: The full event body reaches you through POST to your endpoint, and the affected resource is wrapped under data.object:
{
"id": "evt_2M",
"object": "event",
"type": "signature_request.completed",
"created_at": 1754500000,
"livemode": true,
"data": {
"object": {
"id": "sr_3n8Kd2Qa1V",
"object": "signature_request",
"status": "completed",
"legal_level": "aes",
"reference": "WTQ-1A2B3C4D5E6F",
"completed_at": 1754500000,
"download_url": "https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/download"
}
}
}
Verify the signature header Wthaiq-Signature on every incoming request, and return the response 2xx quickly, and run the heavy work asynchronously.
Download the signed contract and verify it
As soon as the status becomes completed, request the final signed contract data. This endpoint is available for completed requests only and returns JSON containing pdf_url (the final PDF with the verification certificate and its reference, downloaded with a secret key) and final_document_url (the final document page). The independent cryptographic proof for this path is the the sealed evidence record (JSON), not a PAdES signature embedded inside the PDF — download it through GET /v1/signature_requests/{id}/evidence.
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/download \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# {"object":"download","final_document_url":"https://wthaiq.com/final/…","reference":"WTQ-1A2B3C4D5E6F","pdf_url":null,…}
To verify the contract's integrity publicly, use Reference code WTQ-... assigned on completion. Any party holding the reference can confirm the document's authenticity and integrity — programmatically or through the public page /verify.
curl https://wthaiq.com/api/v1/verifications/WTQ-1A2B3C4D5E6F \
-H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Response
{
"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" }
}
HMAC-SHA256 of the document text at sealing time (not of the downloaded PDF), and the request is sealed with an authenticated evidence record bearing a signature of type Ed25519 (which any party can verify with the public key at /trust) and a certified timestamp to RFC 3161. Any later change to the stored document text changes the hash, turning integrity to modified and tampering is detected immediately. More details in The legal standing page.You have completed your first integration — Where to next?
Go deeper into the full reference, secure how you receive Webhooks, and extend your integration.
Full documentation
The comprehensive reference for the concepts, authentication, error handling and pagination.
Open the documentationWebhooks
Receive events in real time and verify the signature with the header Wthaiq-Signature.
API reference
Every endpoint, object, field and error is documented in detail with live examples.
Browse the referenceLibraries
Official libraries: PHP · Node.js · Python · iOS · Android.
See the librariesIntegrations
Connect Wthaiq to your tools and systems through ready-made integrations and automated workflows.
Browse the integrationsLegal standing and trust
Understand the SES, AES and QES levels, the PAdES standards and the Egyptian legal framework.
Learn moreDeveloper questions before you start.
Is there a test environment?
Yes. Create a test key sk_test_ from the developer dashboard with the “Test key” option: it charges no balance, sends no invitation or reminder to anyone, accepts your account email as the signer, goes no further than a simple signature, and its objects carry livemode: false. For use in the browser without exposing full permissions, use a publishable key pk_ — limited to specific paths and locked to your domains.
What is the difference between SES, AES and QES for me as a developer?
You control the level through the two fields legal_level and method. The simple level, SES, is a drawn signature with a verification code sent by email. The advanced level, AES, adds documented identity verification (an official document and a live face match through a specialized identity-verification provider) that ties the signature to a real person — enable it by setting require_identity: true. The qualified QES level uses a certificate held on a hardware token (method: token) from a licensed certification authority, and carries the highest legal standing under Egyptian Electronic Signature Law No. 15 of 2004. Fuller details in The legal standing page.
Does signing happen inside my application?
Both options are open to you. The easiest is to use signing_url hosted, which is returned with every signer. And if you want an embedded experience under your own brand without leaving your app, request a short-lived signing session via POST /v1/signers/{id}/signing_session and embed it in your interface. See API reference for the details of embedded signing (White-label).
How do I secure my keys?
Use the secret key on the server only, and never place it in web or mobile client code or in a public repository. Store it in environment variables or a secrets vault, restrict it to the least possible privilege, and rotate it regularly. If you suspect it has leaked, revoke it immediately from the developer console and create a new one. Also secure your Webhook endpoints by verifying the header Wthaiq-Signature. See Security guide.
Which SDK languages are supported?
Five official libraries: three for server integration (PHP · Node.js · Python) and two for in-app signing (iOS · Android). Their repositories live on the github.com/wthaiq organization. They follow semantic versioning (SemVer), and the server libraries pin the API version header automatically. The full list and the install commands are on The SDK page.
Ready for your first signed contract?
Start now with a free test key and run the full flow in minutes, with your own email as the recipient of the first test.