Overview
QSignHub lets you integrate qualified electronic signatures directly into your application via a clean REST API. Every signature request goes through a complete identity verification cycle, cryptographic signing using a qualified certificate issued by an EU-trusted CA, and timestamping via an accredited TSA.
Signature types
Authentication
The API uses API keys in the X-API-Key header to authorize requests. Generate keys from the developer dashboard.
Production keys have the prefix qsh_live_, test keys use qsh_test_.
# Required on every request X-API-Key: qsh_live_Xk9mN3pQr7vT2wL8yE5uH1jA4cB6dF0g X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 # optional, recommended for POST
Rate limiting
Rate limit headers are included in every response:
X-RateLimit-Limit: 1000 # requests per minute (Startup plan) X-RateLimit-Remaining: 847 # remaining in current window X-RateLimit-Reset: 1735689600 # Unix timestamp of window reset X-RateLimit-Tier: "startup" # active plan tier
Signing Flow
The standard qualified signature process consists of five steps:
Signature Requests
| Field | Type | Description |
|---|---|---|
| document_id required | string | ID of a document previously uploaded via POST /documents |
| signers required | array[Signer] | List of signers. Min 1, max 20. |
| signature_type optional | enum | qes | aes | ses. Defaults to qes. |
| signing_order optional | enum | sequential | parallel. Defaults to parallel. |
| expires_at optional | datetime | ISO 8601. Request expiry deadline. Defaults to 30 days from creation. |
| redirect_url optional | string | URL to redirect the signer to after signing is complete. |
| fields optional | array[Field] | Signature field positions on the document (page, x, y, width, height). |
| metadata optional | object | Arbitrary key-value pairs. Returned in webhook payloads. |
{
"document_id": "doc_9Xk3mN7pQr2vT8wL",
"signature_type": "qes",
"signing_order": "sequential",
"expires_at": "2025-03-01T23:59:59Z",
"redirect_url": "https://yourapp.com/contract/success",
"signers": [
{
"name": "Anna Kowalska",
"email": "anna.kowalska@company.com",
"phone": "+48500123456",
"identity_verification": {
"method": "video_id", // video_id | eidas_idp | bank_id
"country": "PL"
},
"locale": "en-GB",
"order": 1
},
{
"name": "John Smith",
"email": "john.smith@company.com",
"order": 2
}
],
"fields": [
{
"signer_index": 0,
"page": 3,
"x": 72, "y": 640,
"width": 200, "height": 80,
"type": "signature"
}
],
"metadata": {
"contract_id": "CNT-2025-0042",
"department": "legal"
}
}
{
"id": "sreq_7Yx4kP9nQ3mB2wR1",
"status": "pending",
"signature_type": "qes",
"document_id": "doc_9Xk3mN7pQr2vT8wL",
"signing_url": "https://sign.qsignhub.io/r/7Yx4kP9nQ3mB2wR1", // hosted signing page
"embed_token": "eyJhbGciOiJSUzI1NiJ9...", // for SDK iframe embed
"signers": [
{
"id": "sgn_A1b2C3d4E5f6",
"name": "Anna Kowalska",
"email": "anna.kowalska@company.com",
"status": "pending_verification",
"signing_url": "https://sign.qsignhub.io/s/A1b2C3d4E5f6"
}
],
"expires_at": "2025-03-01T23:59:59Z",
"created_at": "2025-01-15T10:34:22Z"
}
Returns the full state of a signature request, including per-signer statuses and certificate metadata.
{
"id": "sreq_7Yx4kP9nQ3mB2wR1",
"status": "completed",
"signature_type": "qes",
"signed_document_url": "https://cdn.qsignhub.io/docs/sreq_7Yx4.../signed.pdf",
"audit_trail_url": "https://cdn.qsignhub.io/audit/sreq_7Yx4....pdf",
"signers": [
{
"id": "sgn_A1b2C3d4E5f6",
"name": "Anna Kowalska",
"status": "signed",
"signed_at": "2025-01-15T11:02:44Z",
"certificate": {
"issuer": "Krajowa Izba Rozliczeniowa S.A.",
"serial": "3A:F2:9C:11:00:BB:EE:44",
"valid_from": "2024-06-01",
"valid_to": "2026-06-01",
"qualified": true,
"tsp": "CERTUM Certification Centre"
},
"timestamp": {
"value": "2025-01-15T11:02:45.321Z",
"authority": "CERTUM TSA",
"hash": "sha256:e3b0c44298fc1c149afb..."
}
}
],
"completed_at": "2025-01-15T11:02:46Z"
}
Request statuses
| Query param | Type | Description |
|---|---|---|
| status | string | Filter by status: pending, completed, expired, cancelled |
| from | datetime | ISO 8601 — start of date range |
| to | datetime | ISO 8601 — end of date range |
| limit | integer | Results per page. Max 100, default 20. |
| cursor | string | Pagination cursor (from next_cursor in previous response) |
Cancellation is irreversible. Only requests with status pending or in_progress can be cancelled. Signers will receive an email notification.
{ "reason": "Contract terms updated — a revised version will be sent shortly." }
Documents
Accepts multipart/form-data. Supported formats: PDF (recommended), DOCX, XLSX, XML. Maximum file size: 50 MB. Documents are retained for 90 days from upload.
curl -X POST https://api.qsignhub.io/v1/documents -H "X-API-Key: qsh_live_Xk9mN3pQr7..." -F "file=@service_agreement.pdf" -F "name=Q1 2025 Service Agreement" -F "locale=en-GB"
{
"id": "doc_9Xk3mN7pQr2vT8wL",
"name": "Q1 2025 Service Agreement",
"mime_type": "application/pdf",
"size_bytes": 248320,
"pages": 4,
"sha256": "a4b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5...",
"preview_url": "https://cdn.qsignhub.io/preview/doc_9Xk3.../page-1.png",
"created_at": "2025-01-15T10:30:00Z",
"expires_at": "2025-04-15T10:30:00Z"
}
Returns the signed PDF with embedded digital signatures (PAdES-LTV), a TSA timestamp, and an attached Audit Trail. Only available when request status is completed.
?include=audit_trail query parameter returns a ZIP archive containing: the signed PDF, an HTML action history report, identity verification screenshots, and all signer certificates.Signers
Only possible when the request is in pending status. The new signer will receive an email invitation link.
| Field | Type | Description |
|---|---|---|
| name required | string | Full name of the signer |
| email required | string | Email address |
| phone optional | string | Phone number in E.164 format (required for SMS OTP) |
| identity_verification.method | enum | video_id · eidas_idp · bank_id · sms_otp |
| identity_verification.country | string | ISO 3166-1 alpha-2 country code |
| locale optional | string | BCP 47 locale (e.g. en-GB, de-DE, pl-PL) |
{
"signer_id": "sgn_A1b2C3d4E5f6",
"verification_status": "verified", // pending | in_progress | verified | failed
"method_used": "video_id",
"verified_at": "2025-01-15T10:58:12Z",
"identity": {
"full_name": "ANNA MARIA KOWALSKA", // from identity document
"document_type": "national_id", // passport | national_id | drivers_license
"document_country": "PL",
"date_of_birth": "1985-04-22",
"document_expiry": "2030-11-15",
"match_score": 0.97 // face match score 0.0-1.0
},
"liveness_check": true
}
Sends a reminder to a signer. Limit: 3 times per signer, no more than once every 24 hours.
{
"channel": "email", // email | sms | both
"message": "Please sign the document before the end of this week."
}
Certificates & Validation
Verifies the integrity of an electronic signature on a PDF document. Works for documents signed via QSignHub as well as any other eIDAS-compliant system (PAdES, XAdES, CAdES).
curl -X POST https://api.qsignhub.io/v1/validate -H "X-API-Key: qsh_live_..." -F "file=@signed_agreement.pdf"
{
"valid": true,
"signatures": [
{
"signer_name": "Anna Maria Kowalska",
"signed_at": "2025-01-15T11:02:45.321Z",
"signature_level": "QES",
"certificate_valid": true,
"certificate_issuer": "Krajowa Izba Rozliczeniowa S.A.",
"trusted_list": "EU_TRUSTED", // present on EU TSL
"document_integrity": true, // document unaltered since signing
"timestamp_valid": true,
"ocsp_status": "good" // good | revoked | unknown
}
]
}
Webhooks
Configure an HTTPS endpoint in the dashboard to receive real-time events. QSignHub authenticates events using an HMAC-SHA256 signature in the X-QSignHub-Signature header.
Events
// POST https://yourapp.com/webhooks/qsignhub // X-QSignHub-Signature: sha256=a4f2b1c9d8e7f6a5... { "id": "evt_Z9wX8vU7tS6rQ5pO", "type": "signature_request.completed", "created_at": "2025-01-15T11:02:50Z", "data": { "signature_request": { "id": "sreq_7Yx4kP9nQ3mB2wR1", "status": "completed", "signed_document_url": "https://cdn.qsignhub.io/docs/.../signed.pdf", "metadata": { "contract_id": "CNT-2025-0042" } } } }
HMAC Signature Verification
const crypto = require('crypto'); function verifyWebhook(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(payload, 'utf8') .digest('hex'); const received = signature.replace('sha256=', ''); return crypto.timingSafeEqual( Buffer.from(expected, 'hex'), Buffer.from(received, 'hex') ); }
Error Codes
{
"error": {
"code": "DOCUMENT_TOO_LARGE",
"message": "File exceeds the 50 MB limit. Received: 62.3 MB.",
"docs_url": "https://docs.qsignhub.io/errors/DOCUMENT_TOO_LARGE",
"request_id": "req_V1W2X3Y4Z5A6B7C8" // for support reference
}
}
Sandbox Environment
The sandbox is fully isolated from production. Test keys use the prefix qsh_test_. No real signatures are generated — all identity checks and certificates are mocked.
sign.success@qsignhub-test.io (auto-success), sign.fail@qsignhub-test.io (failed verification), sign.timeout@qsignhub-test.io (no signer action).Test identity document numbers
Identity Providers (eIDAS IdP)
When using the eidas_idp method, the signer is redirected to their national identity provider. Supported providers and countries:
GET /v1/idp/countries