The official Wthaiq libraries: open source, typed, and designed to make integration a matter of minutes. Install the package with a single command, configure the client with your key, and create your first signature request without writing an HTTP layer by hand. The library takes care of safe retries, cursor pagination, API version pinning, and Webhooks signature verification.
All the libraries share the same resource interface and operation names, so what you learn in one language applies to the rest. Node.js, Python and PHP are the tier-1 libraries with complete examples, and Go, Ruby and .NET are available alongside them.
TypeScript ready with complete type definitions, and runs on Node and edge runtimes.
npm i @wthaiq/nodeimport Wthaiq from '@wthaiq/node';
const wt = new Wthaiq('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
const sr = await wt.signatureRequests.create({
title: 'Employment contract',
source: { type: 'template', template_id: 'tpl_employment' },
legal_level: 'aes',
signers: [{ name: 'Ahmed Mohamed', email: 'ahmed@example.com',
method: 'draw', require_identity: true }]
});
console.log(sr.id, sr.status); // sr_3n8Kd2Qa1V sentType definitions through type hints and stub files, with async support where needed.
pip install wthaiqimport wthaiq
wt = wthaiq.Client('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
sr = wt.signature_requests.create(
title='Employment contract',
source={'type': 'template', 'template_id': 'tpl_employment'},
legal_level='aes',
signers=[{'name': 'Ahmed Mohamed', 'email': 'ahmed@example.com',
'method': 'draw', 'require_identity': True}],
)
print(sr.id, sr.status) # sr_3n8Kd2Qa1V sentPSR-compliant, works with Laravel, Symfony and any Composer project, with strict types.
composer require wthaiq/wthaiq-php$wt = new \Wthaiq\Client('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
$sr = $wt->signatureRequests->create([
'title' => 'Employment contract',
'source' => ['type' => 'template', 'template_id' => 'tpl_employment'],
'legal_level' => 'aes',
'signers' => [[
'name' => 'Ahmed Mohamed', 'email' => 'ahmed@example.com',
'method' => 'draw', 'require_identity' => true,
]],
]);
echo $sr->id; // sr_3n8Kd2Qa1VYou do not need to rebuild the networking and security logic. Every library implements the following capabilities in the same way, so you get a consistent experience across your projects.
On network errors or transient responses (429/5xx) the library retries with exponential backoff, and automatically attaches an Idempotency-Key with every POST request so that no creation is duplicated.
Iterate over thousands of records without managing cursors by hand. The iterator fetches the following pages automatically via starting_after depending on next_cursor andhas_more.
A ready-made helper function (constructEvent) verifies the header Wthaiq-Signature with a constant-time comparison, enforces a 300-second tolerance, and then returns the verified event object.
API errors are translated into typed exceptions that match the error envelope: AuthenticationError andInvalidRequestError andRateLimitError and others, with code andparam andrequest_id.
Every library sends the header Wthaiq-Version: 2026-07-01 pinned with every request, so your integrations are unaffected by any later changes. You can override the version per client or per request.
Configure the connect and read timeouts, the number of retries and the HTTP client used (an enterprise proxy, for example) for each client, to suit your environment and your reliability requirements.
Choose your language and copy the example directly: create a signature request, iterate through lists with automatic pagination, and verify the Webhook signature before processing.
import Wthaiq from '@wthaiq/node';
import { randomUUID } from 'node:crypto';
const wt = new Wthaiq(process.env.WTHAIQ_API_KEY);
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' }
}, { idempotencyKey: randomUUID() });
console.log(sr.id, sr.status); // sr_3n8Kd2Qa1V sent// The iterator fetches the following pages automatically using the cursor
for await (const sr of wt.signatureRequests.list({ status: 'completed', limit: 100 })) {
console.log(sr.id, sr.reference);
}import express from 'express';
const app = express();
// Pass the raw body for signature verification
app.post('/hooks/wthaiq', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['wthaiq-signature'];
let event;
try {
event = wt.webhooks.constructEvent(req.body, sig, process.env.WTHAIQ_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`signature check failed: ${err.message}`);
}
if (event.type === 'signature_request.completed') {
const sr = event.data.object; // signature_request object
// Activate the account or store the signed document (run the heavy work later)
}
res.json({ received: true });
});import os, uuid, wthaiq
wt = wthaiq.Client(os.environ['WTHAIQ_API_KEY'])
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'},
idempotency_key=str(uuid.uuid4()),
)
print(sr.id, sr.status) # sr_3n8Kd2Qa1V sent# auto_paging_iter walks through every page automatically
for sr in wt.signature_requests.list(status='completed', limit=100).auto_paging_iter():
print(sr.id, sr.reference)import os, wthaiq
from flask import Flask, request
app = Flask(__name__)
endpoint_secret = os.environ['WTHAIQ_WEBHOOK_SECRET']
@app.post('/hooks/wthaiq')
def handle():
payload = request.get_data()
sig = request.headers.get('Wthaiq-Signature')
try:
event = wthaiq.Webhook.construct_event(payload, sig, endpoint_secret)
except wthaiq.error.SignatureVerificationError:
return 'invalid signature', 400
if event.type == 'signature_request.completed':
sr = event.data.object # signature_request object
# Activate the account or store the signed document
return {'received': True}require 'vendor/autoload.php';
$wt = new \Wthaiq\Client(getenv('WTHAIQ_API_KEY'));
$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'],
], ['idempotency_key' => bin2hex(random_bytes(16))]);
echo $sr->id . ' ' . $sr->status; // sr_3n8Kd2Qa1V sent// autoPagingIterator fetches the following pages automatically using the cursor
foreach ($wt->signatureRequests->all(['status' => 'completed', 'limit' => 100]) as $sr) {
echo $sr->id . ' ' . $sr->reference . "\n";
}require 'vendor/autoload.php';
$payload = file_get_contents('php://input');
$sig = $_SERVER['HTTP_WTHAIQ_SIGNATURE'] ?? '';
$secret = getenv('WTHAIQ_WEBHOOK_SECRET');
try {
$event = \Wthaiq\Webhook::constructEvent($payload, $sig, $secret);
} catch (\Wthaiq\Exception\SignatureVerificationException $e) {
http_response_code(400);
exit('invalid signature');
}
if ($event->type === 'signature_request.completed') {
$sr = $event->data->object; // signature_request object
// Activate the account or store the signed document
}
http_response_code(200);Wthaiq-Signature: t=...,v1=... and the secret whsec_..., compares it with a constant-time comparison and rejects any request whose time difference exceeds 300 seconds.We commit to predictable limits on change so that you can plan your upgrades with confidence, and we separate the library version from the API version.
Every library follows semantic versioning MAJOR.MINOR.PATCH. Breaking changes are only introduced in a new MAJOR version; compatible additions and bug fixes ship safely in MINOR and PATCH releases.
The library version is independent of the API version pinned through Wthaiq-Version, so you can upgrade the library without changing API behaviour.
API changes are dated through the version header, and any released version stays supported. When an old capability is deprecated we announce it in the changelog and allow a transition period of at least 12 months before removal.
Deprecation warnings are also emitted through response headers and library logs, so you know early what needs updating. See Changelog regularly.
| Language | Minimum runtime requirements | Package source | Version |
|---|---|---|---|
| Node.js | Node.js 18+ | npm · @wthaiq/node | v1.x |
| Python | Python 3.8+ | PyPI · wthaiq | v1.x |
| PHP | PHP 8.1+ | Packagist · wthaiq/wthaiq-php | v1.x |
| Go | Go 1.21+ | pkg.go.dev · wthaiq/wthaiq-go | v1.x |
| Ruby | Ruby 3.0+ | RubyGems · wthaiq | v1.x |
| .NET | .NET 6.0+ | NuGet · Wthaiq | v1.x |
Wthaiq-Version: 2026-07-01 by default, and connects tohttps://wthaiq.com/api/v1, and authenticate through Authorization: Bearer sk_.... There is no test mode — every sk_ live as soon as it is created; the publishable key pk_... for safe use in the browser, and remains limited to specific paths and domains.We publish six official libraries: Node.js, Python and PHP as tier-1 libraries with complete examples and broader support, plus Go, Ruby and .NET. All of them share the same resource interface and operation names, so what you learn in one language applies directly to the rest.
Yes, all the libraries are open source and published under the organisation github.com/wthaiq, and you can follow the code, open issues and contribute. The packages are distributed through their standard registries: npm, PyPI, Packagist, pkg.go.dev, RubyGems and NuGet.
The library retries automatically on network errors and transient responses (429 and 5xx) with exponential backoff, and attaches to every POST request the key Idempotency-Key unique. Because the key is stored on the server for 24 hours, no retry creates a duplicate resource. You can also pass your own key with each request.
Every library sends the header Wthaiq-Version: 2026-07-01 pinned by default with every request, so API responses keep the same shape despite any later updates. You can override the version when configuring the client, or per request, whenever you want to adopt a newer version after testing it.
Minimum: Node.js 18, Python 3.8, PHP 8.1, Go 1.21, Ruby 3.0 and .NET 6.0. The libraries follow SemVer, so there are no breaking changes except in a new major release. When a capability is discontinued we announce it in the changelog and allow a transition period of at least 12 months before removal.
Start with the library for your language and follow the quickstart guide to create your first signature request in minutes — with built-in types and full legal standing.