# 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 %}
