مرجع واجهة برمجة التطبيقات للشركاء
مرجع كامل لبناء تكاملات الشركاء مع منصة هوية للتحقق من الهوية. يشمل المصادقة والتحقق من الهويات والأحداث والتقارير.
Overview
Base URL
https://{your-domain}/api/partner/v1
API Version
v1
Content-Type
application/json
Auth Scheme
OAuth 2.0 mTLS (RFC 8705)
All partner API endpoints are strictly scoped to the authenticated partner's tenant. Your OAuth 2.0 access token is bound to your tenant ID and client certificate, which are enforced on every request — you can only access verification cases, webhook configurations, and resources belonging to your organisation.
All responses use camelCase JSON property names. Enum values are serialised as strings (e.g. "Approved", not integers). All timestamps are ISO 8601 with UTC offset, for example 2026-03-23T14:00:00+00:00.
Every request and response is correlated by a X-Correlation-ID header. If you supply one on your requests it will be echoed back and appear in all server-side logs and audit records, making cross-system debugging straightforward.
Authentication
All Partner API requests are authenticated using OAuth 2.0 with Mutual TLS (mTLS) as defined in RFC 8705. Partners authenticate with a registered X.509 client certificate at the TLS layer and obtain short-lived, certificate-bound access tokens via the client_credentials grant. Access tokens are scoped to the partner's tenant and bound to the certificate thumbprint (cnf.x5t#S256).
Authentication Flow
PartnerAdmin role. Each client must have a valid X.509 certificate. Up to 5 active OAuth clients are allowed per tenant. Access tokens expire after 1 hour (3600 seconds).OAuth 2.0 mTLS Overview
The platform implements OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens per RFC 8705. The external TLS-terminating reverse proxy validates the client certificate and forwards the SHA-256 certificate thumbprint via the X-Client-Cert-Thumbprint header. The platform verifies that the thumbprint matches the registered OAuth client and the issued access token.
Token format
hwy_at_<64 lowercase hex characters>
Example: hwy_at_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2Example authenticated API request
GET /api/partner/v1/verifications HTTP/1.1
Host: huwiyya.syria-cloud.sy
Authorization: Bearer hwy_at_a1b2c3d4e5f6...your-token...
Content-Type: application/jsoncURL with mTLS
# Obtain access token using client certificate
curl -X POST https://huwiyya.syria-cloud.sy/api/partner/v1/oauth/token \
--cert client.pem --key client-key.pem \
-d "grant_type=client_credentials&client_id=hwy_a1b2c3d4..."
# Use the access token with mTLS
curl -X GET https://huwiyya.syria-cloud.sy/api/partner/v1/verifications \
--cert client.pem --key client-key.pem \
-H "Authorization: Bearer hwy_at_..." \
-H "Content-Type: application/json"Register OAuth Client
Register a new OAuth client by uploading the client's X.509 certificate in PEM format. The platform extracts the SHA-256 thumbprint, subject DN, and validity period. A unique client_id is generated and returned. Requires PartnerAdmin role via the Partner Portal.
/api/partner/v1/oauth/clientsRegister a new OAuth client with a client certificate. Returns the generated client_id.
| Parameter | Type | Description |
|---|---|---|
| clientNamerequired | string | Human-readable name for this OAuth client (e.g. "Production mTLS Client"). |
| certificatePemrequired | string | X.509 client certificate in PEM format (-----BEGIN CERTIFICATE----- block). |
| allowedScopes | string | Space-delimited scopes. Default: "verifications webhooks evidence reports". |
Response 201
{
"client": {
"id": "c1d2e3f4-a5b6-7890-c1d2-e3f4a5b67890",
"clientId": "hwy_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"clientName": "Production mTLS Client",
"certificateThumbprint": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
"certificateSubjectDN": "CN=partner.example.com, O=Partner Corp",
"certificateNotBefore": "2026-01-01T00:00:00+00:00",
"certificateNotAfter": "2027-01-01T00:00:00+00:00",
"allowedScopes": "verifications webhooks evidence reports",
"isActive": true,
"createdAt": "2026-03-23T14:00:00+00:00"
},
"clientId": "hwy_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"
}Obtain Access Token
Obtain a certificate-bound access token using the OAuth 2.0 client_credentials grant. The request must be made over mTLS — the platform verifies that the client certificate thumbprint matches the registered OAuth client. The token endpoint uses application/x-www-form-urlencoded content type as required by RFC 6749.
/api/partner/v1/oauth/tokenIssue a certificate-bound access token. Requires mTLS with a registered client certificate.
| Parameter | Type | Description |
|---|---|---|
| grant_typerequired | string | Must be "client_credentials". |
| client_idrequired | string | The client_id returned during registration (hwy_... format). |
| scope | string | Space-delimited scopes. Defaults to client's allowed scopes if omitted. |
cURL example
curl -X POST https://huwiyya.syria-cloud.sy/api/partner/v1/oauth/token \
--cert client.pem --key client-key.pem \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=hwy_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4"Response 200
{
"access_token": "hwy_at_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "verifications webhooks evidence reports",
"cnf": {
"x5t#S256": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
}
}cnf claim in the response confirms the certificate binding per RFC 8705 §3.1. The x5t#S256 value is the SHA-256 thumbprint of the bound client certificate. Subsequent API requests must present the same certificate.Use Access Token
After obtaining an access token, include it in the Authorization header as a Bearer token on all Partner API requests. The mTLS connection must still present the same client certificate — the platform validates that the certificate thumbprint matches the token's bound thumbprint.
Request headers
| Parameter | Type | Description |
|---|---|---|
| Authorizationrequired | string | Bearer hwy_at_... — the access token from the token endpoint. |
| X-Client-Cert-Thumbprintrequired | string | SHA-256 thumbprint of the client certificate (forwarded by TLS proxy). |
| Content-Type | string | application/json for most endpoints. |
| Idempotency-Key | string | Unique key for safe retries on create operations. |
Example request
curl -X GET https://huwiyya.syria-cloud.sy/api/partner/v1/verifications \
--cert client.pem --key client-key.pem \
-H "Authorization: Bearer hwy_at_a1b2c3d4e5f6..." \
-H "Content-Type: application/json"Introspect Token
Inspect the metadata and validity of an access token per RFC 7662. Requires mTLS with a registered client certificate.
/api/partner/v1/oauth/introspectIntrospect an access token. Returns metadata including active status and certificate binding.
| Parameter | Type | Description |
|---|---|---|
| tokenrequired | string | The access token to introspect. |
Response 200 (active)
{
"active": true,
"client_id": "hwy_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"scope": "verifications webhooks evidence reports",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"exp": 1711209600,
"iat": 1711206000,
"token_type": "Bearer",
"cnf": {
"x5t#S256": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
}
}Response 200 (inactive)
{
"active": false
}Revoke Token
Revoke an access token per RFC 7009. Requires mTLS with a registered client certificate. The endpoint always returns 200 OK regardless of whether the token existed.
/api/partner/v1/oauth/revokeRevoke an access token. Always returns 200 OK per RFC 7009.
| Parameter | Type | Description |
|---|---|---|
| tokenrequired | string | The access token to revoke. |
List OAuth Clients
List all registered OAuth clients for your tenant. Returns metadata including certificate thumbprints and activity status. Requires Partner Portal session or a valid OAuth access token.
/api/partner/v1/oauth/clientsList all OAuth clients for your tenant. Metadata only.
Response 200
[
{
"id": "c1d2e3f4-a5b6-7890-c1d2-e3f4a5b67890",
"clientId": "hwy_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"clientName": "Production mTLS Client",
"certificateThumbprint": "b94d27b993...efcde9",
"certificateSubjectDN": "CN=partner.example.com",
"certificateNotAfter": "2027-01-01T00:00:00+00:00",
"isActive": true,
"createdAt": "2026-01-10T09:00:00+00:00",
"lastUsedAt": "2026-03-23T13:45:00+00:00",
"revokedAt": null
}
]Revoke OAuth Client
Permanently revoke an OAuth client. All active access tokens issued to this client are immediately revoked. This action is irreversible. Requires PartnerAdmin role via the Partner Portal.
/api/partner/v1/oauth/clients/{clientEntityId}Permanently revoke an OAuth client and all its tokens. Cannot be undone.
Verifications
Every identity check is a Verification Case — a durable record that advances through a defined state machine from Requested to a terminal state.
Verification State Machine
Create Verification
Creates a new verification case and issues a one-time session token for the end user to begin the capture flow. Fully idempotent when the Idempotency-Key header is supplied.
/api/partner/v1/verificationsCreate a new verification case. Returns the case ID and a one-time session token for the end user.
Request headers
| Parameter | Type | Description |
|---|---|---|
| Authorizationrequired | string | Bearer hwy_at_... — your OAuth 2.0 access token |
| Idempotency-Key | string | Unique key for safe retries. Auto-generated if omitted. UUID or any opaque string (max 128 chars). |
| X-Correlation-ID | string | Trace ID propagated through all logs and audit records. Recommended for production integrations. |
Request body
| Parameter | Type | Description |
|---|---|---|
| documentTyperequired | enum | SyrianNationalId | SyrianPassport — the type of document the user will present. |
| externalReference | string | Your internal identifier for this user (max 200 chars). Stored for correlation, never shown to the end user. |
{
"documentType": "SyrianNationalId",
"externalReference": "user-abc-123"
}Response 201
{
"caseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionToken": "stok_Kj8mNqP3rL9vWxYzA1bCdE2fGhIjKl",
"status": "SessionIssued",
"expiresAt": "2026-03-23T16:00:00+00:00"
}sessionToken is returned exactly once and stored only as a hash server-side. Deliver it to the end user immediately via SMS, deep link, or QR code. It expires in 2 hours.Get Verification Details
Returns the full detail of a verification case including all AI check results (OCR, face match, liveness), evidence metadata (SHA-256 hash, size, type), fraud signals, and reviewer decisions. Tenant-scoped: only returns cases belonging to your organisation.
/api/partner/v1/verifications/{caseId}Get full verification case detail including AI results, evidence metadata, fraud signals, and review decisions.
Response 200
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"externalReference": "user-abc-123",
"status": "Approved",
"documentType": "SyrianNationalId",
"compositeRiskScore": 0.12,
"decisionReason": "All automated checks passed.",
"createdAt": "2026-03-23T14:00:00+00:00",
"updatedAt": "2026-03-23T14:08:31+00:00",
"completedAt": "2026-03-23T14:08:31+00:00",
"session": {
"id": "a2b3c4d5-e6f7-...",
"expiresAt": "2026-03-23T16:00:00+00:00",
"isUsed": true,
"usedAt": "2026-03-23T14:02:10+00:00"
},
"documentEvidence": [
{
"id": "e1f2a3b4-...",
"evidenceType": "DocumentFront",
"contentType": "image/jpeg",
"fileSizeBytes": 524288,
"originalFileName": "front.jpg",
"sha256Hash": "a1b2c3d4e5f6...",
"uploadedAt": "2026-03-23T14:03:50+00:00"
}
],
"selfieEvidence": [ "..." ],
"videoEvidence": [ "..." ],
"ocrResult": {
"modelName": "huwiyya-ocr",
"modelVersion": "1.0.0",
"confidenceScore": 0.96,
"extractedFieldsJson": "{"fullName":"Ahmad Al-Souri","idNumber":"12345678"}",
"mrzIsValid": null,
"mrzRawText": null,
"hasManipulationIndicators": false,
"inferenceTimestamp": "2026-03-23T14:06:10+00:00",
"inferenceLatencyMs": 834
},
"faceMatch": {
"modelName": "huwiyya-facematch",
"modelVersion": "1.0.0",
"similarityScore": 0.94,
"threshold": 0.85,
"isMatch": true,
"inferenceTimestamp": "2026-03-23T14:06:11+00:00",
"inferenceLatencyMs": 421
},
"liveness": {
"modelName": "huwiyya-liveness",
"modelVersion": "1.0.0",
"livenessScore": 0.98,
"threshold": 0.75,
"isLive": true,
"presentationAttackDetected": false,
"attackType": null,
"inferenceTimestamp": "2026-03-23T14:06:13+00:00",
"inferenceLatencyMs": 612
},
"fraudSignals": [],
"reviewDecisions": []
}List Verifications
/api/partner/v1/verificationsPaginated list of verification cases for your tenant. Filter by status.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| status | enum | Filter by status. Values: Requested · SessionIssued · CaptureStarted · EvidenceUploaded · AutomatedChecksRunning · PendingManualReview · Approved · Rejected · Expired · Cancelled · Failed |
| page | integer | Page number, 1-based (default: 1). |
| pageSize | integer | Items per page, max 100 (default: 50). |
Response 200
{
"items": [
{
"id": "3fa85f64-...",
"externalReference": "user-abc-123",
"status": "Approved",
"documentType": "SyrianNationalId",
"compositeRiskScore": 0.12,
"createdAt": "2026-03-23T14:00:00+00:00",
"completedAt": "2026-03-23T14:08:31+00:00"
}
],
"page": 1,
"pageSize": 50,
"totalCount": 147,
"totalPages": 3
}Cancel Verification
Cancels an active case. Only non-terminal states can be cancelled: Requested, SessionIssued, CaptureStarted, and PendingManualReview. The reason is stored in the audit trail.
/api/partner/v1/verifications/{caseId}/cancelCancel a verification case. Provide a human-readable reason for audit purposes.
{
"reason": "User withdrew consent."
}Update OCR Fields
Correct or amend AI-extracted OCR fields on a verification case. This is useful when automated extraction produced inaccurate results and the partner needs to supply corrected values. Every update creates a full audit trail entry with before/after comparison. Only non-terminal cases or cases in PendingManualReview can be updated.
/api/partner/v1/verifications/{caseId}/ocr-fieldsUpdate AI-extracted OCR fields. Creates an audit trail with before/after comparison.
| Parameter | Type | Description |
|---|---|---|
| fieldsrequired | object | Key-value map of field names to corrected values. Keys must match existing extracted field names. |
| notes | string | Optional free-text note explaining the reason for the correction. Stored in audit trail. |
{
"fields": {
"fullName": "أحمد السوري",
"idNumber": "12345678"
},
"notes": "Corrected name transliteration from OCR output."
}Response 200
{
"modelName": "huwiyya-ocr",
"modelVersion": "1.0.0",
"confidenceScore": 0.96,
"extractedFieldsJson": "{\"fullName\":\"أحمد السوري\",\"idNumber\":\"12345678\"}",
"mrzIsValid": null,
"mrzRawText": null,
"hasManipulationIndicators": false,
"inferenceTimestamp": "2026-03-23T14:06:10+00:00",
"inferenceLatencyMs": 834
}Evidence Access
Download evidence files (document images, selfie photos, liveness video) associated with verification cases belonging to your tenant. Files are streamed directly from secure object storage with proper content-type headers. Access is tenant-scoped and logged in the audit trail.
Download Evidence File
Stream an evidence file by its ID. The response content-type matches the original upload (e.g. image/jpeg, video/mp4). Evidence IDs are returned in the verification detail response.
/api/partner/v1/evidence/{evidenceId}/fileStream an evidence file from object storage. Returns the file with its original content-type.
evidenceId from the verification detail response. The file is streamed directly — no JSON envelope. Use the Content-Type response header to determine the file type.Webhook Endpoints
Configure outbound webhook endpoints to receive real-time event notifications when verification cases change state. Each endpoint has an HMAC-SHA256 signing secret so you can verify payload authenticity before processing.
List Endpoints
/api/partner/v1/webhooksList all webhook endpoints configured for your tenant.
[
{
"id": "b1c2d3e4-5678-4abc-d9ef-012345678901",
"url": "https://yourbank.com/events/huwiyya",
"eventTypes": ["verification.approved", "verification.rejected"],
"isActive": true,
"createdAt": "2026-01-15T10:00:00+00:00"
}
]Get Endpoint
/api/partner/v1/webhooks/{id}Get a single webhook endpoint by ID.
Create Endpoint
Creates a webhook endpoint and returns the HMAC signing secret. The plain-text secret is returned exactly once — store it in your secret manager immediately. It cannot be retrieved again; use rotate-secret if you lose it.
/api/partner/v1/webhooksCreate a new webhook endpoint. The signing secret is returned once in the response.
Request body
| Parameter | Type | Description |
|---|---|---|
| urlrequired | string | HTTPS endpoint URL. Must be publicly reachable. Max 500 chars. |
| eventTypesrequired | string[] | Array of event type strings to subscribe to. See Event Types reference. |
{
"url": "https://yourbank.com/events/huwiyya",
"eventTypes": [
"verification.approved",
"verification.rejected",
"verification.failed",
"verification.pending_manual_review"
]
}Response 201
{
"endpoint": {
"id": "b1c2d3e4-5678-4abc-d9ef-012345678901",
"url": "https://yourbank.com/events/huwiyya",
"eventTypes": ["verification.approved", "verification.rejected"],
"isActive": true,
"createdAt": "2026-03-23T14:00:00+00:00"
},
"signingSecret": "whsec_Kj8mNqP3rL9vWxYzA1bCdE2fGhIjKlMnOpQrStUvWxYz"
}Update Endpoint
/api/partner/v1/webhooks/{id}Update the target URL and/or subscribed event types for a webhook endpoint.
{
"url": "https://yourbank.com/events/huwiyya-v2",
"eventTypes": ["verification.approved", "verification.failed"]
}Activate / Deactivate
/api/partner/v1/webhooks/{id}/deactivatePause delivery to this endpoint without deleting it.
/api/partner/v1/webhooks/{id}/activateResume delivery to a previously deactivated endpoint.
Rotate Signing Secret
Generates a new HMAC signing secret for the endpoint. The new plain-text secret is returned once. Rotate secrets periodically or immediately after a potential compromise. Update your receiver code with the new secret before rotating to avoid verification failures during the transition.
/api/partner/v1/webhooks/{id}/rotate-secretGenerate a new HMAC signing secret. The previous secret is invalidated immediately.
{
"newSigningSecret": "whsec_NewRotatedSecretReturnedOnce..."
}Delivery History
/api/partner/v1/webhooks/{id}/deliveriesPaginated delivery history for a webhook endpoint, including HTTP status codes and retry counts.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1). |
| pageSize | integer | Items per page (default: 20). |
Response 200
{
"deliveries": [
{
"id": "d1e2f3a4-...",
"eventType": "verification.approved",
"caseId": "3fa85f64-...",
"httpStatus": 200,
"attemptCount": 1,
"lastAttemptAt": "2026-03-23T14:09:00+00:00",
"succeeded": true
}
],
"page": 1,
"pageSize": 20,
"totalCount": 1,
"totalPages": 1
}Reports
Summary Statistics
Returns verification case counts grouped by status for a specified date range. Useful for dashboard KPIs, SLA tracking, and monitoring automation rates.
/api/partner/v1/reports/summaryVerification case counts grouped by status for a date range, plus derived automation rate.
Query parameters
| Parameter | Type | Description |
|---|---|---|
| from | ISO 8601 | Start of range (inclusive). Defaults to 30 days ago. |
| to | ISO 8601 | End of range (inclusive). Defaults to now. |
Response 200
{
"from": "2026-02-23T00:00:00+00:00",
"to": "2026-03-23T23:59:59+00:00",
"totalCases": 847,
"byStatus": {
"Approved": 612,
"Rejected": 91,
"PendingManualReview": 23,
"Failed": 8,
"Expired": 113
},
"automationRate": 0.93
}Export CSV
/api/partner/v1/reports/exportExport all verification cases for a date range as a UTF-8 CSV file. Maximum 5,000 rows per export.
| Parameter | Type | Description |
|---|---|---|
| from | ISO 8601 | Start of range. |
| to | ISO 8601 | End of range. |
The response has Content-Type: text/csv and a filename of the form verifications_YYYYMMDD.csv.
CSV columns
CaseId,Status,DocumentType,ExternalReference,CreatedAt,CompletedAt,CompositeRiskScore,DecisionReasonWebhook Events
Event Types
Subscribe to one or more event types when creating a webhook endpoint. Only subscribed event types are delivered. We recommend subscribing to the states most relevant to your workflow rather than all events.
| Event Type | When fired |
|---|---|
| verification.session_issued | Case created and session token issued to partner |
| verification.capture_started | End user opened the session in the mobile app |
| verification.evidence_uploaded | All required evidence uploaded; AI checks enqueued |
| verification.automated_checks_running | AI pipeline started processing the case |
| verification.pending_manual_review | Automated checks flagged the case; awaiting human review |
| verification.approved | Case approved (automated or after manual review) |
| verification.rejected | Case rejected (automated or after manual review) |
| verification.expired | Session or case expired before completion |
| verification.cancelled | Case cancelled by your system or an administrator |
| verification.failed | Unrecoverable system error during AI processing |
Payload Structure
Each delivery is a POST request to your endpoint URL with a JSON body containing the event type, a unique event ID, a timestamp, and the verification case snapshot. Your endpoint must return any 2xx status code within 30 seconds to acknowledge receipt. Failed deliveries are retried with exponential backoff up to 5 attempts.
{
"eventType": "verification.approved",
"eventId": "evt_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"timestamp": "2026-03-23T14:09:00+00:00",
"apiVersion": "v1",
"tenantId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"data": {
"caseId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"externalReference": "user-abc-123",
"status": "Approved",
"documentType": "SyrianNationalId",
"compositeRiskScore": 0.12,
"decisionReason": "All automated checks passed.",
"completedAt": "2026-03-23T14:08:31+00:00"
}
}Signature Verification
Every delivery includes HTTP headers for verifying payload authenticity. Always verify the signature before processing any webhook payload.
| Header | Value |
|---|---|
| X-Huwiyya-Signature | sha256=<hex> — HMAC-SHA256 of the signed payload using your endpoint's secret |
| X-Huwiyya-Timestamp | Unix timestamp (seconds). Reject events outside ±5 minutes to prevent replay attacks. |
| X-Huwiyya-Event | The event type string (e.g. verification.approved) |
Signed payload: The HMAC is computed over {timestamp}.{rawBodyBytes} where timestamp is the value of X-Huwiyya-Timestamp.
Node.js / Express example
const crypto = require('crypto');
function verifyHuwiyyaSignature(req, signingSecret) {
const signature = req.headers['x-huwiyya-signature']; // "sha256=abc123..."
const timestamp = req.headers['x-huwiyya-timestamp']; // Unix epoch seconds
if (!signature || !timestamp) return false;
// Reject replays older than 5 minutes
const ageSeconds = Math.abs(Date.now() / 1000 - parseInt(timestamp, 10));
if (ageSeconds > 300) return false;
// req.rawBody must be the raw Buffer — do NOT parse before verifying
const computed = 'sha256=' + crypto
.createHmac('sha256', signingSecret)
.update(timestamp + '.' + req.rawBody)
.digest('hex');
// Constant-time comparison prevents timing attacks
return crypto.timingSafeEqual(
Buffer.from(computed, 'utf8'),
Buffer.from(signature, 'utf8'),
);
}
// Express route — use express.raw() to preserve the raw body
app.post(
'/events/huwiyya',
express.raw({ type: 'application/json' }),
(req, res) => {
if (!verifyHuwiyyaSignature(req, process.env.HUWIYYA_WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Handle event.eventType, event.data ...
res.sendStatus(200);
},
);Python example
import hashlib, hmac, time
def verify_huwiyya_signature(
headers: dict,
raw_body: bytes,
signing_secret: str,
) -> bool:
signature = headers.get('X-Huwiyya-Signature', '') # "sha256=abc123..."
timestamp = headers.get('X-Huwiyya-Timestamp', '')
if not signature or not timestamp:
return False
# Reject replays older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
payload = timestamp.encode() + b'.' + raw_body
expected = 'sha256=' + hmac.new(
signing_secret.encode(),
payload,
hashlib.sha256,
).hexdigest()
# Constant-time comparison to prevent timing attacks
return hmac.compare_digest(expected, signature)Error Codes
All error responses follow the RFC 7807 Problem Details format with an additional code field for programmatic handling.
{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Conflict",
"status": 409,
"detail": "A verification case with this idempotency key already exists.",
"code": "CASE_EXISTS"
}| HTTP | Error Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Request body or query parameter validation failed |
| 401 | UNAUTHENTICATED | Missing, expired, or invalid authorization token |
| 403 | FORBIDDEN | Authenticated user lacks the required role or tenant scope |
| 403 | ACCOUNT_DISABLED | Partner account disabled by an administrator |
| 404 | NOT_FOUND | Resource does not exist or belongs to a different tenant |
| 409 | CASE_EXISTS | Idempotency key already used — original case ID in response detail |
| 429 | RATE_LIMITED | Exceeded 120 requests/minute. Check Retry-After header. |
| 429 | ACCOUNT_LOCKED | Account temporarily locked after repeated failed logins |
| 500 | INTERNAL_ERROR | Unhandled server error. Contact the platform operator. |
Rate Limiting
The API enforces a limit of 120 requests per IP address per minute using a fixed-window counter. When exceeded, the API returns HTTP 429 with a Retry-After header indicating the seconds until the window resets.
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/problem+json
{
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Retry after the indicated interval.",
"code": "RATE_LIMITED"
}Machine-to-machine integrations should implement exponential backoff when receiving 429 responses. The most common cause of rate-limit hits is polling the list or status endpoints too aggressively — prefer webhooks for real-time state change notifications.
Idempotency
All POST endpoints that create resources accept an Idempotency-Key request header. Supplying the same key on a retry returns the original successful response without creating a duplicate resource.
POST /api/partner/v1/verifications HTTP/1.1
Authorization: Bearer hwy_at_a1b2c3d4e5f6...your-token...
Idempotency-Key: 7f9c8a1b-3d2e-4f5a-b6c7-d8e9f0a1b2c3
Content-Type: application/json
{
"documentType": "SyrianNationalId",
"externalReference": "user-abc-123"
}- →The same key on a successful retry returns the original 201 response — no duplicate case created.
- →If a case already exists with that key, the API returns 409 CASE_EXISTS with the original case ID in the response detail.
- →Keys should be unique UUIDs generated per real user action, not per HTTP retry attempt.
- →If omitted, a random key is auto-generated and no idempotency protection is applied for retries.
هل أنت مستعد للتكامل؟
تواصل معنا للحصول على بيانات اعتماد الشراكة والبدء في التحقق من الهويات بمستوى بنكي.