For developers · quickstart guide

From zero to your first signed contract in minutes.

This guide takes you step by step from creating an API key to downloading a legally binding signed contract and verifying it — in five direct steps. The examples are ready to copy in cURL, Node, Python and PHP. Note: there is no test mode — every key is live and every call is genuinely billed, so start with a small balance and your own email address as the recipient of the first test.

A prepaid balance in Egyptian pounds SDKs for Node, Python and PHP A sealed, verifiable evidence record
201 Created A signed contract
create-signature-request.sh
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}
    ]
  }'
Before you begin

Three things you need to get started.

Simple preparation you can finish in a minute, then move straight to your first API call.

A Wthaiq account

Register an account on the platform to reach the dashboard. From there you manage your keys, your templates, your signature requests and your event logs.

A secret API key

Create a key in the format sk_ from the dashboard. It gives you full access to the API — and the key is live from the very first moment, so every call you make with it is real and billed.

A plan that supports the API

Programmatic access is available on the plans that include the API. See Pricing page to choose the plan that suits your usage volume.

There is no test mode. Every key sk_ live from the moment it is created: it sends real emails to the signers, issues binding contracts with legal standing, and charges your balance for real (EGP 20 per signature request, plus EGP 35 for each signer who requires identity verification). To try it safely: top up a small balance and use your own email address as the recipient of your first test.
Five steps

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.

1 Authentication

Get your API key

From the dashboard, open Dashboard ← Developers ← API Keys and create a new key. You get two types of key, and there is no test mode — every key works on your real data immediately:

KeyDescription
sk_...A secret key, for the server only, with full privileges. It genuinely sends the messages, issues binding contracts with legal standing, and its cost counts towards your usage from the very first call.
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 behaviour of the API stays stable across updates.

Never expose the secret key in client-side code. The secret key (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 dashboard and create a new one. See Security guide for the details.
2 Configuration

Install the SDK and configure the client

Our official first-class libraries are available for Node, Python and PHP, and they handle authentication, retries and API version pinning automatically. Or work with the API directly over cURL, without any package.

cURLNodePythonPHP
shell
# cURL needs no SDK — just set your key
export WTHAIQ_API_KEY="sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Check connectivity by fetching the ready-made templates
curl https://wthaiq.com/api/v1/templates \
  -H "Authorization: Bearer $WTHAIQ_API_KEY" \
  -H "Wthaiq-Version: 2026-07-01"
terminal + index.js
npm i @wthaiq/node

import Wthaiq from '@wthaiq/node';
const wt = new Wthaiq('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
terminal + main.py
pip install wthaiq

import wthaiq
wt = wthaiq.Client('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
terminal + index.php
composer require wthaiq/wthaiq-php

$wt = new \Wthaiq\Client('sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
Browse the full list of libraries (Go, Ruby and .NET as well) in The SDK page. Every library follows SemVer and pins the API version header automatically.
3 Creation

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).

POST /v1/signature_requests
cURLNodePythonPHP
create.sh
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"}
  }'
create.js
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);
create.py
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)
create.php
$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:

201 Created · application/json
{
  "id": "sr_3n8Kd2Qa1V",
  "object": "signature_request",
  "livemode": false,
  "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://sign.wthaiq.com/s/uZ8..",
      "identity": { "status": "pending", "provider": "didit", "level": "aes" }
    }
  ]
}
idThe request ID, with the prefix sr_. Use it in all subsequent calls (tracking, download, cancellation).
statusThe status of the request. It starts as sent after sending, and passes through viewed and partially_signed up to completed.
livemodetrue always — there is no test mode. The request has genuinely been sent, and a binding certificate will be issued once signing completes.
signers[].signing_urlThe URL of the hosted signing page. Open it for the signer directly, or embed it in your application through a scoped signing session.
signers[].identityThe identity verification status via Didit (official document + live face match) — it must be approved before they can sign, because we requested require_identity.
referencenull now. The public verification reference is assigned WTQ-... automatically when signing completes.
4 Tracking

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.

GET /v1/signature_requests/{id}
poll.sh
curl https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V \
  -H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Wthaiq-Version: 2026-07-01"
The recommended method: Webhooks. Instead of repeated polling, let Wthaiq notify your server in real time. Register an endpoint and listen for the event 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:

signature_request.completed · POST body
{
  "id": "evt_2M",
  "object": "event",
  "type": "signature_request.completed",
  "created_at": 1754500000,
  "livemode": false,
  "data": {
    "object": {
      "id": "sr_3n8Kd2Qa1V",
      "object": "signature_request",
      "status": "completed",
      "legal_level": "aes",
      "reference": "WTQ-000123",
      "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.

5 Download and verification

Download the signed contract and verify it

As soon as the status becomes completed, download the final signed and sealed PDF file. This endpoint is available only for completed requests and returns application/pdf. The independent cryptographic evidence for this path is the sealed evidence record (JSON), not a PAdES signature embedded inside the PDF — download it through GET /api/evidence.php?scope=api&doc=<id>.

GET /v1/signature_requests/{id}/download
download.sh
curl -L https://wthaiq.com/api/v1/signature_requests/sr_3n8Kd2Qa1V/download \
  -H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -o contract-signed.pdf

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‎.

GET /v1/verifications/{reference}
verify.sh
curl https://wthaiq.com/api/v1/verifications/WTQ-000123 \
  -H "Authorization: Bearer sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Response
{
  "object": "verification",
  "reference": "WTQ-000123",
  "found": true,
  "status": "completed",
  "integrity": "intact",
  "document": { "title": "Employment contract", "sha256": "a3f1..", "format": "pades-lt", "legal_level": "aes" }
}
How is integrity guaranteed? The hash is computed SHA-256 of the document at sealing time, 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 a single byte changes the hash, turning integrity to modified and tampering is detected immediately. More details in The legal standing page.
FAQs

Developer questions before you start.

Is there a test environment?

No. There is no separate test mode — every sk_ Live from the moment it is created: it sends real emails to signers, issues legally effective certificates, and charges your balance with every call. The safe way to test: top up a small balance and use your own email address as the recipient. For browser use without exposing full privileges, 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 Didit) 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 dashboard and create a new one. Also secure your Webhook endpoints by verifying the header Wthaiq-Signature. See Security guide.

Which SDK languages are supported?

The official first-class libraries are Node.js, Python and PHP, and libraries are also available for Go, Ruby and .NET. All of them are open source under the organisation github.com/wthaiq, follow semantic versioning (SemVer) and pin the API version header automatically. The full list and the installation commands are in The SDK page.

Ready for your first signed contract?

Create your key now and run the full flow in minutes — with a small balance and your own email as the recipient of the first test.