> ## Documentation Index
> Fetch the complete documentation index at: https://developer.utgl.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Request Signing

> Learn how to authenticate API requests using RSA key pairs and JWT tokens

Access to the Issuing API is authenticated using an API access key combined with cryptographically signed API requests.

***

## Overview

To authenticate your implementation against the Issuing API, you must complete three steps:

1. **Generate an RSA key pair** - Create a public/private RSA key pair in PEM format (minimum 2048 bits)
2. **Register your public key** - Email your public key and IP whitelist address to your implementation manager. In return, you'll receive an Access Key (UUID). In the future, this can be done via the UTGL Developer Portal.
3. **Sign your requests** - Use your access key and private key to generate signed JWT tokens for each API request

### Authentication Flow

The following diagram illustrates the complete authentication flow from initial setup to making authenticated API requests:

```mermaid theme={null}
sequenceDiagram
    participant Dev as Developer
    participant Portal as UTGL Developer Portal
    participant API as Issuing API
    
    Note over Dev: Step 1: Generate RSA Key Pair
    Dev->>Dev: Generate RSA key pair<br/>(public + private)
    Note over Dev: Store private key securely
    
    Note over Dev,Portal: Step 2: Register Public Key
    Dev->>Portal: Email public key + IP whitelist
    Portal->>Dev: Return Access Key (UUID)
    Note over Dev: Store Access Key securely
    
    Note over Dev,API: Step 3: Sign and Send Request
    Dev->>Dev: Prepare API request<br/>(method, URI, body)
    Dev->>Dev: Create JWT claims:<br/>- sub: Access Key<br/>- iat: current time<br/>- exp: iat + 30s<br/>- uri: request URI<br/>- method: HTTP method<br/>- body: SHA256(body) if POST
    Dev->>Dev: Sign JWT with<br/>RSA private key (RS256)
    Dev->>API: HTTP Request with<br/>Authorization: Bearer <JWT>
    
    Note over API: Step 4: Verify Request
    API->>API: Extract JWT from header
    API->>API: Verify signature using<br/>registered public key
    API->>API: Validate claims:<br/>- Check expiration<br/>- Verify URI matches<br/>- Verify method matches<br/>- Verify body hash (if POST)
    
    alt Signature Valid & Claims Match
        API->>Dev: 200 OK + Response Data
    else Signature Invalid or Claims Mismatch
        API->>Dev: 401 Unauthorized<br/>(INVALID_SIGNATURE)
    end
```

***

## Step 1: Generate RSA Key Pair

Generate a public/private RSA key pair using OpenSSL or your preferred tool.

<Warning>
  **Security Warning:** Ensure your private key is stored in a secure, encrypted location and never expose it to anyone.

  Gaining access to your access key + private key essentially grants full access to your account.
</Warning>

<CodeGroup>
  ```shell Generate RSA Key Pair theme={null}
  openssl req -new -newkey rsa:4096 -nodes \
  	-keyout utgl-access.private -out utgl-access.csr
  ```
</CodeGroup>

***

## Step 2: Create Bearer Token

Each API request requires a Bearer token in the `Authorization` header. The token is a signed JWT that includes request-specific claims.

### Process Overview

1. **Create the JWT header and payload** - Include required claims (URI, method, body hash, etc.)
2. **Sign the JWT** - Use your RSA private key with the RS256 algorithm
3. **Add to request header** - Include the signed token as `Authorization: Bearer <token>`

The Issuing API verifies the JWT signature against the provided claims and validates the URI, method, and request body against the claims.

### JWT Creation Process

```mermaid theme={null}
flowchart TD
    A[Start: Prepare API Request] --> B{Request has body?}
    B -->|Yes| C[Hash request body<br/>with SHA-256]
    B -->|No| D[Create JWT Claims]
    C --> D
    D --> E[Add required claims:<br/>sub, iat, exp, uri, method]
    E --> F{Body hash exists?}
    F -->|Yes| G[Add body claim]
    F -->|No| H[Create JWT Header:<br/>alg: RS256, typ: JWT]
    G --> H
    H --> I[Encode header + payload<br/>as Base64URL]
    I --> J[Sign with RSA private key<br/>using RS256 algorithm]
    J --> K[Create Bearer Token:<br/>header.payload.signature]
    K --> L[Add to Authorization header]
    L --> M[Send HTTP Request]
    
    style A fill:#e0e0e0,stroke:#616161,stroke-width:2px
    style M fill:#c8e6c9,stroke:#388e3c,stroke-width:2px
    style J fill:#fff9c4,stroke:#f57f17,stroke-width:2px
```

<Warning>
  If token verification fails, the API returns **HTTP 401 Unauthorized** with error code `INVALID_SIGNATURE`.
</Warning>

***

## JWT Token Specification

The JWT token must be created according to the following specification.

### Header

The JWT header must contain:

| Field | Value   | Description         |
| ----- | ------- | ------------------- |
| `alg` | `RS256` | Signature algorithm |
| `typ` | `JWT`   | Token type          |

### Payload (Claims)

The JWT payload must include the following claims:

| Claim    | Required    | Description                                                                                       | Example                                                            |
| -------- | ----------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `sub`    | Yes         | Your Access Key (UUID)                                                                            | `899a7a89-bb6b-4d43-a702-c6aa45dd89cf`                             |
| `iat`    | Yes         | Issued at time (Unix timestamp in seconds)                                                        | `1668849961`                                                       |
| `exp`    | Yes         | Expiration time (Unix timestamp in seconds). Must be less than `iat + 30` seconds                 | `1668849991`                                                       |
| `body`   | Conditional | Hex-encoded SHA-256 hash of the raw HTTP request body. **Optional** for GET requests (empty body) | `93a23971a914e5eacbf0a8d25154cda309c3c1c72fbb9914d47c60f3cb681588` |
| `uri`    | Yes         | The URI part of the request, including query parameters                                           | `/v1/transactions?filter=123`                                      |
| `method` | Yes         | The HTTP method of the request                                                                    | `GET`, `POST`, `PUT`, `DELETE`                                     |

### Signing

The JWT must be signed using your **RSA Private Key** with the **RS256** algorithm.

<Tip>
  **Implementation Examples:** See our language-specific guides for complete implementation examples:

  * [Java Authentication Guide](/issuing/recipes/authentication-java)
  * [PHP Authentication Guide](/issuing/recipes/authentication-php)
  * [Go Authentication Guide](/issuing/recipes/authentication-golang)
  * [Node.js Authentication Guide](/issuing/recipes/authentication-nodejs)
</Tip>

***

## Example: GET Request

The following example demonstrates how to create a JWT for a GET request to `/ping`.

### Test Credentials

<CodeGroup>
  ```text Access ID theme={null}
  6e33a078-99ed-4aa1-8e67-b0e19e9475fd
  ```

  ```text RSA Private Key theme={null}
  -----BEGIN PRIVATE KEY-----
  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
  g4bz5IXenzx97yCcOzKb7t6fBUj2EQM=
  -----END PRIVATE KEY-----
  ```
</CodeGroup>

### JWT Components

<CodeGroup>
  ```json Header theme={null}
  {
    "alg": "RS256",
    "typ": "JWT"
  }
  ```

  ```json Claims theme={null}
  {
    "method": "GET",
    "uri": "/ping",
    "iat": 1668849961,
    "exp": 1668849991,
    "sub": "6e33a078-99ed-4aa1-8e67-b0e19e9475fd"
  }
  ```

  ```text Signature theme={null}
  CPrXoy1dR-6wrm9eSkvcqZs5E3sK9jRlAkm1mUJM252A_SgcqPa4A5B-90-NNmV6DBRhNTVxj8iv2xwPnV28MGkOJ0WtkyUAe-nNvQfjrmPkjeHPPbYv_PM4nCLxOF9vfiU2Iz6aKbiLZdqUs78SpOJ1VNMXOe8uOHkmy2f9rFCtUwDS6JlVill1z7TAQr4JYKo42JghplrXHsn-zVzMnTNjaFduK9MbGT4lQnvxd2hNbDu2XqS8_N0POUdGmLdvLTiERPWgmkE62uCqIdMVFchrG_qkeqRuxzowUHuotrMYNRfJrbXWLKSGSyd4igaRAeEHKeAmH2AeAm4GYqS5VHsgHWVq8PfYb_L7I_SCRHXRSU7lq_ZI8wimFgyzfYTPdqOtDReA4hm8BzoW9FuVL7VJf5inPQuHIq6NchpDXFbRGLHnMcvVTM4lzUdN8DHkaS1uQY_MwCfu6uvgUGjUvifKu7bDeJK0RPoQxjF8Yb7DBtTjv3y6NpCCImvUbzVlUYkV3OxjMfnwayqUi6p8JjpuTURoMvg0Jw4yoEhThpuOcDKQZVhJpnam5FDPbfvJ1DtPrQXqW1qfWJLk2ev7SETJ94Lyv3dxMptdOGX2UxUOngjcDwjKmxQ7-iFOsR4BVyJmB0g8PcpTtDspzGD2YJhlgwbhupy_eIgf4UpsGo4
  ```

  ```text Complete Bearer Token theme={null}
  eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI2ZTMzYTA3OC05OWVkLTRhYTEtOGU2Ny1iMGUxOWU5NDc1ZmQiLCJ1cmkiOiIvcGluZyIsIm1ldGhvZCI6IkdFVCIsImV4cCI6MTY2ODg0OTk5MSwiaWF0IjoxNjY4ODQ5OTYxfQ.CPrXoy1dR-6wrm9eSkvcqZs5E3sK9jRlAkm1mUJM252A_SgcqPa4A5B-90-NNmV6DBRhNTVxj8iv2xwPnV28MGkOJ0WtkyUAe-nNvQfjrmPkjeHPPbYv_PM4nCLxOF9vfiU2Iz6aKbiLZdqUs78SpOJ1VNMXOe8uOHkmy2f9rFCtUwDS6JlVill1z7TAQr4JYKo42JghplrXHsn-zVzMnTNjaFduK9MbGT4lQnvxd2hNbDu2XqS8_N0POUdGmLdvLTiERPWgmkE62uCqIdMVFchrG_qkeqRuxzowUHuotrMYNRfJrbXWLKSGSyd4igaRAeEHKeAmH2AeAm4GYqS5VHsgHWVq8PfYb_L7I_SCRHXRSU7lq_ZI8wimFgyzfYTPdqOtDReA4hm8BzoW9FuVL7VJf5inPQuHIq6NchpDXFbRGLHnMcvVTM4lzUdN8DHkaS1uQY_MwCfu6uvgUGjUvifKu7bDeJK0RPoQxjF8Yb7DBtTjv3y6NpCCImvUbzVlUYkV3OxjMfnwayqUi6p8JjpuTURoMvg0Jw4yoEhThpuOcDKQZVhJpnam5FDPbfvJ1DtPrQXqW1qfWJLk2ev7SETJ94Lyv3dxMptdOGX2UxUOngjcDwjKmxQ7-iFOsR4BVyJmB0g8PcpTtDspzGD2YJhlgwbhupy_eIgf4UpsGo4
  ```
</CodeGroup>

***

## Example: POST Request

The following example demonstrates how to create a JWT for a POST request with a request body.

### Request Details

* **Endpoint:** `POST /ping`
* **Request Body:** `{"hello":"world"}`
* **Access ID:** `ed63e5a1-3e8e-4b63-96b5-b711f91bc2dd`

### JWT Components

<CodeGroup>
  ```json Header theme={null}
  {
    "alg": "RS256",
    "typ": "JWT"
  }
  ```

  ```json Request Body theme={null}
  {
    "hello": "world"
  }
  ```

  ```json Claims theme={null}
  {
    "method": "POST",
    "uri": "/ping",
    "body": "93a23971a914e5eacbf0a8d25154cda309c3c1c72fbb9914d47c60f3cb681588",
    "iat": 1668851011,
    "exp": 1668851041,
    "sub": "ed63e5a1-3e8e-4b63-96b5-b711f91bc2dd"
  }
  ```

  ```text Signature theme={null}
  AAZQgrDlAkujb4XsGzhoMc9oU0aKFDbPYI6BQxKdEO3XDBph3jeaDoAXmPgheydQPpKCKuuDYCpzlPAJmVRfvZye1L0PTvyPBWZh10fHwoUQ-sFT0IQeZppTBD6Q2TwBUiLoxbb3Ucmw0RMiQ-XHpLNeJrYrrtbzZqr1KC9TwSxaqbMDcRYnd2h8QxfH-k2iz6JsfuER2Bk6W6EY83NllHrLCmWntuBV118CBo_4heDiocGQ6KEjvKP5N4_ozsh25uJEzRkCvkrIY2v5c92kVn2cInvBCpuOI3NpQJy6FFA0CSsbJ5UmS6IwMktywFIZG7suRJTyE6SUT1UqfK9W9cE5K9TbpJGoBt7laCeiH2oJHm9-IXJUGAAIHRL5V2FVLVEyFB7f9NdrD5tG-8aMFXbSBA4dTkD-88ocFwlB9Q7Yt90XMn5UviFuCAOHqf3L3VK2ZxC_OSiXvi9h1eV0sNWhJBFZ5xBMyZVP_EL2OcjQCmxgU1CwFKRFmc9cP47vMXod7VZv4twPKxy55QSmuGXHZDEb9sG-Sc4Pp-2rrOuwPULkFSPOaGNWSNjYLQn54QKkZiR0MrNbZcpTBaWV2C3PR7kh9PPd6G-u8Pn9l9twuqE78S_qbVf6VENWRUFx7AlscEZlQyUc45RWiRmaPKFAvbcH3f6WZ4jVr6QdwVE
  ```

  ```text Complete Bearer Token theme={null}
  eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXRob2QiOiJQT1NUIiwidXJpIjoiL3BpbmciLCJib2R5IjoiOTNhMjM5NzFhOTE0ZTVlYWNiZjBhOGQyNTE1NGNkYTMwOWMzYzFjNzJmYmI5OTE0ZDQ3YzYwZjNjYjY4MTU4OCIsImlhdCI6MTY2ODg1MTAxMSwiZXhwIjoxNjY4ODUxMDQxLCJzdWIiOiJlZDYzZTVhMS0zZThlLTRiNjMtOTZiNS1iNzExZjkxYmMyZGQifQ.AAZQgrDlAkujb4XsGzhoMc9oU0aKFDbPYI6BQxKdEO3XDBph3jeaDoAXmPgheydQPpKCKuuDYCpzlPAJmVRfvZye1L0PTvyPBWZh10fHwoUQ-sFT0IQeZppTBD6Q2TwBUiLoxbb3Ucmw0RMiQ-XHpLNeJrYrrtbzZqr1KC9TwSxaqbMDcRYnd2h8QxfH-k2iz6JsfuER2Bk6W6EY83NllHrLCmWntuBV118CBo_4heDiocGQ6KEjvKP5N4_ozsh25uJEzRkCvkrIY2v5c92kVn2cInvBCpuOI3NpQJy6FFA0CSsbJ5UmS6IwMktywFIZG7suRJTyE6SUT1UqfK9W9cE5K9TbpJGoBt7laCeiH2oJHm9-IXJUGAAIHRL5V2FVLVEyFB7f9NdrD5tG-8aMFXbSBA4dTkD-88ocFwlB9Q7Yt90XMn5UviFuCAOHqf3L3VK2ZxC_OSiXvi9h1eV0sNWhJBFZ5xBMyZVP_EL2OcjQCmxgU1CwFKRFmc9cP47vMXod7VZv4twPKxy55QSmuGXHZDEb9sG-Sc4Pp-2rrOuwPULkFSPOaGNWSNjYLQn54QKkZiR0MrNbZcpTBaWV2C3PR7kh9PPd6G-u8Pn9l9twuqE78S_qbVf6VENWRUFx7AlscEZlQyUc45RWiRmaPKFAvbcH3f6WZ4jVr6QdwVE
  ```
</CodeGroup>

<Note>
  **Important:** The `body` claim contains the hex-encoded SHA-256 hash of the raw request body. For POST requests, you must include this hash. The exact string representation of the body must match between signing and sending.
</Note>

***

## Common Issues & Troubleshooting

### Token Expiration

JWT tokens expire after 30 seconds. Generate a fresh token for each API request.

### Body Hash Mismatch

Ensure the request body used for signing exactly matches the body sent in the HTTP request. Common issues:

* Trailing whitespace differences
* JSON formatting differences (spaces, newlines)
* Character encoding mismatches

### Invalid Signature Errors

If you receive `INVALID_SIGNATURE` errors:

1. Verify your private key is correct and properly formatted
2. Ensure you're using RS256 algorithm
3. Check that all required claims are present
4. Verify the `uri` includes query parameters if present
5. Confirm the `body` hash matches the actual request body (for POST requests)

***

## Next Steps

* Review [language-specific authentication guides](/recipes) for implementation examples
* Explore the [API Reference](/api-reference) to see available endpoints
* Check [Error Handling](/issuing/getting-started/requests-responses) for detailed error responses
