Common signing patterns
The Due API uses two distinct signing patterns for different operations:
Pattern 1: Direct JSON Signing (Credential Creation Only)
Used only when creating credentials. You construct and sign a JSON object directly.
Flow:
1. API returns: challenge + clientDataHash
2. You create: JSON object with {clientDataHash, publicKey}
3. You sign: SHA256 hash of the JSON
4. Signature format: Hex-encoded
Example: See Create Credential guide
Pattern 2: Challenge-Response Flow (Everything Else)
Used for all other secured operations: wallet creation, transaction signing, credential approval/deactivation, and any future secured endpoints.
Universal Request Structure
All Pattern 2 operations follow the same request/response structure:
Step 1: Initial Request
Send your operation payload:
{
"payload": {
// Your operation-specific data
// Can be empty object {} for some operations like wallet creation
}
}Examples of payloads:
- Wallet creation:
{}(empty payload) - Transaction signing:
{"keyId": "key_xxx", "hash": "0xabc..."} - Credential approval:
{"credentialId": "passkey_xxx"} - Credential deactivation:
{"credentialId": "passkey_xxx"}
Step 2: Challenge Response (403)
The API always returns a 403 error with a challenge:
{
"success": false,
"message": "Please sign the following challenge to proceed",
"httpCode": 403,
"code": "ACTION_SIGNATURE_REQUIRED",
"data": {
"factors": {
"Key": {
"credId": "",
"clientData": "eyJ0eXBlIjoia2V5LmdldCIsImNoYWxsZW5nZS...", // Base64 encoded challenge
"signature": null
}
},
"challengeIdentifier": "chid1DLr5F2ij7oyYLrGy6ekLnouxbE5SiiaN"
}
}Key fields:
clientData: Base64url-encoded challenge data to sign (no padding). When decoding, you may need to add = padding before passing to a standard Base64 decoder, or use a Base64url decoder directly.challengeIdentifier: Unique ID for this challenge (valid for a limited time)
Step 3: Sign the Challenge
Decode and sign the clientData:
# Decode base64 → Sign with SHA256 → Encode to base64 URL-safe format
echo -n "<clientData_from_response>" | base64 -d | openssl dgst -sha256 -sign private.pem | base64 -w 0 | tr '+/' '-_' | tr -d '='Note: openssl dgst -sha256 -sign performs hashing and signing in a single step — it takes raw input, computes the SHA-256 hash internally, and signs the result. If your library's sign function also hashes internally (e.g. Python's ECDSA(SHA256())), pass the decoded clientData bytes directly. Do not hash first and then sign, as this would result in double-hashing.
Important: The signature must be in base64 URL-safe format (not hex like Pattern 1).
Step 4: Retry with Signature
Send the same request with both payload and signature:
{
"payload": {
// Same payload as Step 1
},
"signature": {
"challengeIdentifier": "<challengeIdentifier_from_step2>",
"firstFactor": {
"kind": "Key",
"credentialAssertion": {
"credId": "<your_credential_id>",
"clientData": "<original_clientData_from_step2>",
"signature": "<your_base64_url_safe_signature>"
}
}
}
}Response: Success with operation-specific result.
Complete Example: Vault Creation
Request 1 (Initial):
curl -X POST 'https://api.due.network/v1/vaults' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{}' # Empty payload for vault creationResponse 1 (Challenge):
{
"success": false,
"httpCode": 403,
"code": "ACTION_SIGNATURE_REQUIRED",
"data": {
"factors": {
"Key": {
"clientData": "eyJ0eXBlIjoia2V5LmdldCIsImNoYWxsZW5nZS..."
}
},
"challengeIdentifier": "chid1DLZSWZlpvQSGTmMrwhUZTpCIFOwaneRi"
}
}Sign:
echo -n "eyJ0eXBlIjoia2V5LmdldCIsImNoYWxsZW5nZS..." | base64 -d | openssl dgst -sha256 -sign private.pem | base64 -w 0 | tr '+/' '-_' | tr -d '='Request 2 (With Signature):
curl -X POST 'https://api.due.network/v1/vaults' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"signature": {
"challengeIdentifier": "chid1DLZSWZlpvQSGTmMrwhUZTpCIFOwaneRi",
"firstFactor": {
"kind": "Key",
"credentialAssertion": {
"credId": "passkey_xonETR6gAv3wIyhy8ehjx",
"clientData": "eyJ0eXBlIjoia2V5LmdldCIsImNoYWxsZW5nZS...",
"signature": "MEUCIAlkmdPEf0B_5xVr4DKK6uvsN0y1YVzsFe4vAOMlPcn4AiEA..."
}
}
}
}'Response 2 (Success):
{
"id": "key_2lRmxX5KBYRzMg",
"address": "0x08bEB4Ad3D8D607646E4d5311eb355Ec2d2396F5"
}Operations Using Pattern 2
| Operation | Endpoint | Payload Structure |
|---|---|---|
| Create Wallet | POST /v1/vaults | {} (empty) |
| Sign Transaction | POST /v1/vaults/sign | {"keyId": "<wallet_id>", "hash": "..."} |
| Approve Credential | POST /v1/vaults/credentials/approve | {"credentialId": "..."} |
| Deactivate Credential | POST /v1/vaults/credentials/deactivate | {"credentialId": "..."} |
| Activate Credential | POST /v1/vaults/credentials/activate | {"credentialId": "..."} |
Note: In Sign Transaction, keyId is the Due Wallet ID (e.g., key_2lRmxX5KBYRzMg) returned when creating the wallet.
Sequence Diagram
sequenceDiagram
participant Client
participant API as Due API
participant Signer as Private Key
Note over Client,API: Pattern 1: Credential Creation
Client->>API: POST /credentials/init
API-->>Client: challenge + clientDataHash
Client->>Client: Create JSON {clientDataHash, publicKey}
Client->>Signer: Sign SHA256(JSON)
Signer-->>Client: Hex signature
Client->>API: POST /credentials with signature
API-->>Client: Credential created
Note over Client,API: Pattern 2: Challenge-Response (All Other Operations)
Client->>API: POST /endpoint with {payload}
API-->>Client: 403 with base64 challenge
Client->>Client: Decode base64 challenge
Client->>Signer: Sign SHA256(decoded)
Signer-->>Client: Base64 URL-safe signature
Client->>API: POST /endpoint with {payload, signature}
API-->>Client: Success responseUpdated about 1 month ago