# Exchange Transaction Signature

This section describes the process where the Relying Party (RP) backend exchanges the **sign code** received from the Singpass for the **user's digital signature**. This step finalises the transaction signing workflow, ensuring the transaction is securely signed and validated.

## Exchange signature API

### URLs

<table><thead><tr><th width="165">Environment</th><th width="176">Access Mechanism</th><th>URL</th></tr></thead><tbody><tr><td>Staging</td><td>mTLS</td><td>https://stg-id.singpass.gov.sg:8443/txn-signatures<br><mark style="color:red;">(Deprecated)</mark></td></tr><tr><td>Staging</td><td>TLS</td><td><a href="https://staging.sign.singpass.gov.sg/api/v1/txn-signatures
">https://staging.sign.singpass.gov.sg/api/v1/txn-signatures</a></td></tr><tr><td>Production</td><td>mTLS</td><td>https://id.singpass.gov.sg:8443/txn-signatures<br><mark style="color:red;">(Deprecated)</mark></td></tr><tr><td>Production</td><td>TLS</td><td><a href="https://app.sign.singpass.gov.sg/api/v1/txn-signatures
">https://app.sign.singpass.gov.sg/api/v1/txn-signatures</a></td></tr></tbody></table>

### Client Authentication

RPs are required to authenticate themselves by specifying a signed JWT via the `Authorization` header.

&#x20;**JWT header**

The following standard JWS headers need to be included. Refer to [Jose Header RFC](https://tools.ietf.org/html/rfc7515#section-4) for more details about what each header presents.

**JWT claims**

The following claims need to be present.

<table><thead><tr><th width="146">Path</th><th width="145">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>sub</code></td><td><code>String</code></td><td>The Client ID generated during transaction signing replying party registration.</td></tr><tr><td><code>sign_code</code></td><td><code>String</code></td><td>The Singpass generated code that was returned to the RP via the RP’s redirect URI (specified in the <code>sign_code</code> query parameter).</td></tr><tr><td><code>iat</code></td><td><code>Number</code></td><td>The time at which the JWT was issued.</td></tr></tbody></table>

Claims example

```json
{
  "sign_code": "aRpFkeEbdOSBaeY7aGIk0rkvb90yr9nezLM4cMLOuVc=",
  "iat": 1598238992,
  "sub": "4iHWBHNNrYcXKKMOvk3bIE3CYAQnQ84V"
}
```

### Request

**Request headers**

Should include the Authorization token descibed in [Client Authentication](#client-authentication).

**Request body**

Request body shall be a json object with the sign\_code parameter.

```json
{"sign_code":"wOU1mqb7XICjhdTUgl73"}
```

**Example request**

{% code overflow="wrap" %}

```http
POST https://app.sign.singpass.gov.sg/api/v1/txn-signatures HTTP/1.1
Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJhdWQiOiJodHRwczovL2lkLnNpbmdwYXNzLmdvdi5zZy90eG4tc2lnbmF0dXJlcyIsInN1YiI6Im5kaV9yZWdpc3RlcmVkX2NsaWVudF9pZCIsInNpZ25fY29kZSI6IndPVTFtcWI3WElDamhkVFVnbDczIiwiaXNzIjoibmRpX3JlZ2lzdGVyZWRfY2xpZW50X2lkIiwiZXhwIjoxNzM0MzM4Nzc4LCJpYXQiOjE3MzQzMzg3MTh9.1s2vk5I7o1cPYu2Djbf5qVLO4NmruwgI_L2rVqU7Q3q_b_TclSA4ue637OsiCoLTxMC9g5zDDIEjlz3hykBAq4o9jPOhMu4HsBFsiaz9IYmP2OkNnx3kMxBUxeqKZUxz
Content-Type: application/json

{"sign_code":"wOU1mqb7XICjhdTUgl73"}
```

{% endcode %}

### Response

The response is a JWT signed by Singpass wrapped in a json response.

Example:

{% code overflow="wrap" %}

```json
{"token":"eyJraWQiOiJuZGlfZHNzX3NpZ25lciIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJhdWQiOiJodHRwczovL2F1ZGllbmNlLm9yaWdpbiIsInN1YiI6IjZlNjc2NjU0LTViYjQtNDIyNy1iYTFhLWYzMDU5ZTIyNWYyMCIsInR4bl9oYXNoX3NpZ25hdHVyZSI6IjZhYzc2MzdkYTkyYzc2Mzg1Zjk1YTkyYzc2MTdlNTkxYThmNmRmOGY3NGYzN2VmOGRiN2UyNWU2NDhlMWRiN2UiLCJ0eG5faGFzaCI6IjZhYzc2MzdkYTkyYzc2Mzg1Zjk1YTkyYzc2MTdlNTkxYThmNmRmOGY3NGYzN2VmOGRiN2UyNWU2NDhlMWRiN2UiLCJpc3MiOiJodHRwczovL2NsaWVudC5yZWRpcmVjdC5vcmlnaW4iLCJleHAiOjE3MzQzMzg4MzUsImlhdCI6MTczNDMzODcxNSwibm9uY2UiOiJjZWJhUTRYaVRoczBvUDA4YXJYY0JRcGordnkraTBCUHRhNFllVFhrTEVFPSJ9.vVZxJrZoUoloLzjvCwB8-Cg0yVxlBRQmGDcGrxNy_Owr8n_NV_sy7oQI0FMzLd4XcGBNfhIdLKBuycQIOZCVWPUU6qrhZFVORfjOCyJc10BvtB3_inOP8drRkHb4Q1o5"}
```

{% endcode %}

#### Token JWT structure

**Headers**

The following standard JWS headers will be included. Refer to [Jose Header RFC](https://tools.ietf.org/html/rfc7515#section-4) for more details about what each header presents.

Header Example

```json
{
  "kid" : "abcd",
  "typ" : "JWT",
  "alg" : "ES256"
}
```

**Claims**

The following claims will be returned.

<table><thead><tr><th width="240">Path</th><th width="123">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>iss</code><br><mark style="color:red;">(Deprecated)</mark></td><td><code>String</code></td><td>The principal that issued the JWT. <a href="https://tools.ietf.org/html/rfc7519#section-4.1.1">https://tools.ietf.org/html/rfc7519#section-4.1.1</a><br></td></tr><tr><td><code>sub</code></td><td><code>String</code></td><td>The user’s Singpass user id signed the transaction. <a href="https://tools.ietf.org/html/rfc7519#section-4.1.2">https://tools.ietf.org/html/rfc7519#section-4.1.2</a></td></tr><tr><td><code>iat</code></td><td><code>Number</code></td><td>The time at which the JWT was issued. <a href="https://tools.ietf.org/html/rfc7519#section-4.1.6">https://tools.ietf.org/html/rfc7519#section-4.1.6</a></td></tr><tr><td><code>exp</code></td><td><code>Number</code></td><td>The expiration time on or after which the JWT MUST NOT be accepted by Singpass for processing. Additionally, Singpass will not accept tokens with an <code>exp</code> longer than 2 minutes since <code>iat</code>. <a href="https://tools.ietf.org/html/rfc7519#section-4.1.4">https://tools.ietf.org/html/rfc7519#section-4.1.4</a></td></tr><tr><td><code>nonce</code></td><td><code>String</code></td><td>The value passed by the signing partner during the init session API call</td></tr><tr><td><code>txn_hash_signature</code></td><td><code>String</code></td><td>The hex-encoded user signature over the <code>txn_hash</code></td></tr><tr><td><code>txn_hash</code></td><td><code>String</code></td><td>The SHA-256 hash of the <code>txn_id</code> and <code>txn_instructions</code> field calculated in this manner: <code>sha256(&#x3C;txn_id>:&#x3C;txn_instructions></code>). Encoded in hexadecimal.</td></tr></tbody></table>

Claims Example

{% code overflow="wrap" %}

```json
{
  "sub" : "6e676654-5bb4-4227-ba1a-f3059e225f20",
  "txn_hash_signature" : "6ac7637da92c76385f95a92c7617e591a8f6df8f74f37ef8db7e25e648e1db7e",
  "txn_hash" : "6ac7637da92c76385f95a92c7617e591a8f6df8f74f37ef8db7e25e648e1db7e",
  "exp" : 1734338835,
  "iat" : 1734338715,
  "nonce" : "cebaQ4XiThs0oP08arXcBQpj+vy+i0BPta4YeTXkLEE="
}
```

{% endcode %}

**Signature**

Standard JWT signature, RP **MUST** validate the signature with [Singpass public keys. ](#fetch-our-public-keys)

### Error handling

Singpass APIs are RESTful in design and communicate classes of errors based on the **Http Status** code. The status code should be used to determine if the error is caused by consumer or provider. Consumers should log the HTTP status code along with the `id` and/or `trace_id` of the error.

<table><thead><tr><th width="165">Http Status</th><th>Description</th></tr></thead><tbody><tr><td>4xx</td><td><p>Errors caused by API consumer. You can expect codes such as 400, 401, 403, 404 etc if incorrect requests are made to APIs.</p><p>Example: <strong>400: Invalid/missing request arguments</strong></p></td></tr><tr><td>5xx</td><td><p>Errors caused by API provider or its dependencies. You can expect codes such as 500, 502, 503 etc if there is an issue on Singpass or its dependencies.</p><p>Example: <strong>500: Internal Server Error due to some kind of programming error.</strong></p></td></tr></tbody></table>

**Error response json**

<table><thead><tr><th width="230">Path</th><th width="101">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>String</code></td><td>The unique identifier for this error/request. Please log this identifier for support and debugging purposes.</td></tr><tr><td><code>trace_id</code></td><td><code>String</code></td><td>(Optional) An auxiliary id for request correlation across services. Please also log this identifier for operational support and debugging purposes.</td></tr><tr><td><code>error</code></td><td><code>String</code></td><td>Error code representing broad class of error. See <a href="https://stg-id.singpass.gov.sg/docs/txn-signing/api#api_error_codes">Error Codes</a> for the list of possible error codes that can be returned and what they represent.</td></tr><tr><td><code>error_description</code></td><td><code>String</code></td><td>Returns human readable general information about the reason for the error. Note that due to security reasons; detailed information is unlikely to be available in this message.</td></tr></tbody></table>

**Error codes**

| Error Code            | Description                                                |
| --------------------- | ---------------------------------------------------------- |
| `CLIENT_SIDE_ERROR`   | Generic error code for an invalid request.                 |
| `SERVER_SIDE_ERROR`   | Generic error code for an error that occurred in Singpass. |
| `UNAUTHORIZED`        | Authorisation header value is invalid.                     |
| `ARGUMENTS_NOT_VALID` | Some request parameters are invalid.                       |

Example: Invalid Request Parameters

```http
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "id" : "bcba4bc3-534e-4891-bfa2-e872b4502d80",
  "error" : "CLIENT_SIDE_ERROR",
  "error_description" : "This is an invalid request.",
  "trace_id" : "675fe8742c6442a7c0a1f3d1643ff9af"
}
```

## Fetch our public keys

Integrating parties can verify the signature of a JWT from Singpass by acquiring the signing public key from this endpoint. More information about a JSON Web Key (JWK) endpoint can be found [here](https://tools.ietf.org/html/rfc7517).

Public keys returned from this endpoint could be in random sequence or rotated for security enhancement.&#x20;

### URLs

<table><thead><tr><th width="178">Environment</th><th width="576">URL</th></tr></thead><tbody><tr><td>Staging</td><td><p><a href="https://static.staging.sign.singpass.gov.sg/.well-known/keys.json">https://static.staging.sign.singpass.gov.sg/.well-known/keys.json</a><a href="https://static.staging.sign.singpass.gov.sg/.well-known/keys.json"><br></a>OR</p><p><a href="https://stg-id.singpass.gov.sg/.well-known/digital-signing-keys">https://stg-id.singpass.gov.sg/.well-known/digital-signing-keys</a> <mark style="color:red;">(Deprecated)</mark></p></td></tr><tr><td>Production</td><td><a href="https://static.app.sign.singpass.gov.sg/.well-known/keys.json">https://static.app.sign.singpass.gov.sg/.well-known/keys.json</a><a href="https://static.app.sign.singpass.gov.sg/.well-known/keys.json"><br></a>OR<br><a href="https://id.singpass.gov.sg/.well-known/digital-signing-keys">https://id.singpass.gov.sg/.well-known/digital-signing-keys</a> <mark style="color:red;">(Deprecated)</mark></td></tr></tbody></table>

### Cache and key rotation

For varying reasons, keys used for signing can and will be rotated/changed with **no defined schedule**, and at the full discretion of Singpass. When a key rotation happens, the new key will be available from the JWKS endpoint and will have a different `kid` value. The new `kid` value will be reflected in all the new JWTs signed by Singpass. In such cases, cached copies of Singpass public keys must be refreshed by re-invoking the JWKS endpoint.

If the validation of the Singpass signature fails, re-fetch from the JWKS endpoint once for that validation.

{% hint style="danger" %}
Please read through the list of **DON’Ts** below:

* Do not assume the position of a signing key among the list of the returned keys.
* Do not validate Singpass signatures using a hardcoded public key OR `kid`. Always determine the correct key (for signature verification) by inspecting the `kid` from the JWS header, and use it to retrieve the public key from our JWKS endpoint.
* Do not cache only 1 key. Caching should be done for the entire JWKS.
  {% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sign.singpass.gov.sg/for-relying-parties/api-documentation/transaction-signing/exchange-transaction-signature.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
