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 creation

Response 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

OperationEndpointPayload Structure
Create WalletPOST /v1/vaults{} (empty)
Sign TransactionPOST /v1/vaults/sign{"keyId": "<wallet_id>", "hash": "..."}
Approve CredentialPOST /v1/vaults/credentials/approve{"credentialId": "..."}
Deactivate CredentialPOST /v1/vaults/credentials/deactivate{"credentialId": "..."}
Activate CredentialPOST /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 response