# Sign V1

{% hint style="warning" %}
We are no longer accepting requests to onboard Sign V1. Please refer to the [latest documentation for V3](https://docs.sign.singpass.gov.sg/for-relying-parties/api-documentation/sign-v3) instead.&#x20;
{% endhint %}

## Changelog

<table><thead><tr><th width="174">Date</th><th>Changes</th></tr></thead><tbody><tr><td>30 Sep 2024</td><td><ul><li>Introduction of <a href="#authenticating-via-json-web-token"><mark style="color:blue;">JWT as the preferred authentication mech</mark></a><mark style="color:blue;">anism</mark></li><li>Deprecation of Basic Authentication</li></ul></td></tr><tr><td>11 Nov 2024</td><td>Added IP addresses for whitelisting</td></tr><tr><td>5 Mar 2025</td><td>Update to <a href="#qr-code-deeplinking-specifications">QR Code deeplinking specifications</a> to support new staging app</td></tr><tr><td>27 May 2025</td><td>Update to <a data-mention href="#other-dsap-requirements">#other-dsap-requirements</a></td></tr><tr><td>2 June 2025</td><td>Updated to indicate that <a href="sign-v3">V3 is released</a>; onboarding for v1 no longer supported</td></tr></tbody></table>

## Introduction <a href="#introduction" id="introduction"></a>

The guide provide a clear illustration of the web-based application programming interfaces (API) for the use of Document Signing Application Providers (DSAP). Described here are the necessary APIs that DSAPs must invoke to facilitate a document signing process for a Singpass user.

## Document Signing Flow Diagram <a href="#document-signing-flow-diagram" id="document-signing-flow-diagram"></a>

An overview of the document signing flow and the interactions between DSAP, Singpass and other dependencies.

<figure><img src="https://372330916-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FXnljQgt54HuOtH3cKCRP%2Fuploads%2FVLK2Ua7cHFkzJaWGL24S%2Fimage.png?alt=media&#x26;token=8c98b263-7363-4d79-9e7d-b2a8f658b677" alt=""><figcaption><p>Document signing flow</p></figcaption></figure>

## Staging and Production URLs <a href="#staging-and-production-urls" id="staging-and-production-urls"></a>

**Disclaimer:** The domains used in the sample requests in the API specs below may not be accurate for your environment. Please choose the correct one from the table below that suits your testing needs.

| Environment    | Domain                                          | Access Mechanism                                                                                                                                                     |
| -------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Staging**    | <https://staging.sign.singpass.gov.sg/api/v1/*> | <ul><li>SSL</li><li>Basic Authentication (Deprecated)</li><li>JWT <a href="https://docs.sign.singpass.gov.sg/#authenticating-via-json-web-token">(New)</a></li></ul> |
| **Production** | <https://app.sign.singpass.gov.sg/api/v1/*>     | <ul><li>SSL</li><li>Basic Authentication (Deprecated)</li><li>JWT <a href="https://docs.sign.singpass.gov.sg/#authenticating-via-json-web-token">(New)</a></li></ul> |

{% hint style="warning" %}
You must not do "cert pinning" or create any form of dependency to the **leaf** TLS certificates of the above domains. Singpass reserves the right to rotate its TLS leaf certificate without prior notice to partners.

mTLS is no longer required. We will ignore all incoming mTLS client authentication.
{% endhint %}

#### JWKS URLs

<table><thead><tr><th width="192">Environment</th><th>Domain</th><th>Access Mechanism</th></tr></thead><tbody><tr><td><strong>Staging</strong></td><td><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></td><td>SSL</td></tr><tr><td><strong>Production</strong></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></td><td>SSL</td></tr></tbody></table>

{% hint style="warning" %}
Please note that there may be more than one key appearing in the JWKS, and DSAPs must use the `kid` value to identify the right key.
{% endhint %}

## DSAP Server Certificate <a href="#dsap-server-certificate" id="dsap-server-certificate"></a>

The webhook server must be configured to present the full certificate chain to Singpass when Singpass invokes the notification webhook endpoints. This can be verified using a tool like [SSL Labs](https://www.ssllabs.com/). Please note that your cert should not be a self-signed certificate.

Additionally, server certificates presented must minimally conform to the following requirements:<br>

<table><thead><tr><th width="192">Requirement</th><th>RSA Certs</th><th>ECC Certs</th></tr></thead><tbody><tr><td><strong>Required Public Key dimensions</strong></td><td><p></p><p>>= 2048 bits </p><p>>= 3072 bits (recommended)</p></td><td>>= 256-bit based on curves <code>P-256</code>, <code>P-384</code> or <code>P-521</code> <em>(NIST curves, aka <code>secp256r1</code>, <code>secp384r1</code>, <code>secp521r1</code> respectively)</em></td></tr><tr><td><strong>X.509 v3 extension KeyUsage</strong></td><td>digitalSignature, keyEncipherment</td><td>digitalSignature</td></tr><tr><td><strong>X509 v3 extension ExtendedKeyUsage</strong></td><td>serverAuth</td><td>serverAuth</td></tr></tbody></table>

## General Error Response <a href="#general-error-response" id="general-error-response"></a>

DSS 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="338">HTTP Status Code</th><th>Description</th><th data-hidden>RSA Certs</th></tr></thead><tbody><tr><td><strong>4XX</strong></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></p><p>Example: <strong>400: Invalid/missing request arguments</strong></p></td><td><p></p><p>>= 2048 bits </p><p>>= 3072 bits (recommended)</p></td></tr><tr><td><strong>5XX</strong></td><td><p>Errors caused by DSS or its dependencies. You can expect codes such as 500, 502, 503 etc if there is an issue on DSS or its dependencies.</p><p></p><p>Example: <strong>500: Internal Server Error due to some kind of programming error.</strong></p></td><td>digitalSignature, keyEncipherment</td></tr></tbody></table>

#### **Example: Invalid Request Parameters**

```
HTTP/1.1 400 Bad Request
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Cache-Control: no-cache, no-store, must-revalidate
Transfer-Encoding: chunked
Content-Type: application/json
Date: Tue, 24 Sep 2024 02:30:42 GMT
Content-Length: 190

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

#### **Example: Server Error**

```
HTTP/1.1 500 Internal Server Error
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Cache-Control: no-cache, no-store, must-revalidate
Transfer-Encoding: chunked
Content-Type: application/json
Date: Tue, 24 Sep 2024 02:30:42 GMT
Content-Length: 192

{
  "id" : "bcba4bc3-534e-4891-bfa2-e872b4502d80",
  "error" : "SERVER_SIDE_ERROR",
  "error_description" : "An unexpected error occurred.",
  "trace_id" : "66f22452d4ed47e5b4fbb870813ab8eb"
}
```

### Description of fields <a href="#description-of-fields" id="description-of-fields"></a>

<table><thead><tr><th width="192">Path</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>String</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>String</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>String</td><td>Error code representing broad class of error. See Error Codes 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>String</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<br>

<table><thead><tr><th width="338">Error Code</th><th>Description</th><th data-hidden>RSA Certs</th></tr></thead><tbody><tr><td><code>CLIENT_SIDE_ERROR</code></td><td>Generic error code for an invalid request.</td><td><p></p><p>>= 2048 bits </p><p>>= 3072 bits (recommended)</p></td></tr><tr><td><code>SERVER_SIDE_ERROR</code></td><td>Generic error code for an error that occurred in Singpass.</td><td>digitalSignature, keyEncipherment</td></tr><tr><td><code>UNAUTHORIZED</code></td><td>Authorization header value is invalid.</td><td></td></tr><tr><td><code>MISSING_AUTHORIZATION</code></td><td>Authorization header was required but not found.</td><td></td></tr><tr><td><code>ARGUMENTS_NOT_VALID</code></td><td>Some request parameters are invalid.</td><td></td></tr><tr><td><code>SIGN_REF_NOT_FOUND</code></td><td>Requested sign ref is invalid or has already expired.</td><td></td></tr><tr><td><code>MULTIPLE_ASSERTION_HEADER</code></td><td>Multiple DSS assertion headers.</td><td></td></tr><tr><td><code>ASSERTION_NOT_VALID</code></td><td>DSS assertion header is invalid.</td><td></td></tr><tr><td><code>JWKS_URL_NOT_FOUND</code></td><td>DSAP JWKS URL is not found.</td><td></td></tr><tr><td><code>CLIENT_JWKS_ERROR</code></td><td>Unable to fetch DSAP JWKS or DSAP JWKS is invalid.</td><td></td></tr></tbody></table>

## Document Signing endpoints invoked by DSAP <a href="#document-signing-endpoints-invoked-by-dsap" id="document-signing-endpoints-invoked-by-dsap"></a>

The API in this sections are endpoints invoked by DSAPs at various part of the DSS flow.

* /doc-signing-sessions
* /doc-signing-sessions/\<sign\_ref>/hash
* /.well-known/keys.json

### Authenticating via Basic Authentication (Deprecated)

{% hint style="warning" %}
This is deprecated in favour of [authenticating via JSON Web Tokens.](https://docs.sign.singpass.gov.sg/#authenticating-via-json-web-token-recommended)
{% endhint %}

For all DSS APIs except the JWKS endpoint, DSAPs will need to authenticate themselves using their base64-encoded `client_id` and `client_secret` via the [basic authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#Basic_authentication_scheme) scheme in the following format:

```
Authorization: Basic <client_id>:<client_secret>
```

### Authenticating via JSON Web Token <a href="#authenticating-via-json-web-token" id="authenticating-via-json-web-token"></a>

DSAPs are to use JSON Web Token (JWT) assertion for authentication when calling the DSS APIs.

```
X-Dss-Assertion: <jwt_assertion>
```

#### Structure of JWT

#### Sample JWT

{% code overflow="wrap" %}

```
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJwLXRlc3QtMDEifQ.eyJpc3MiOiJtb2NrQ2xpZW50SWQiLCJzdWIiOiJtb2NrQ2xpZW50SWQiLCJhdWQiOiJodHRwczovL3N0YWdpbmcuc2lnbi5zaW5ncGFzcy5nb3Yuc2ciLCJpYXQiOjE3Mjc2NjUwMzcsImV4cCI6MTcyNzY2NTYzNywianRpIjoiN2Q2NDdmNTMtZDEzOC00YjUxLWIxZDgtZGM2OWQ1NmNhNDAxIn0.iF9WH09CW1zhOhaSH37Faaw6kw7dbUMx2yuIAW9tD7p50ixUWIBTswvwxkutIPFY4zGFxcjrV4USwBfxpe28RA
```

{% endcode %}

**Decoded JWT Header**

```
{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "rp-test-01"
}
```

| Parameter | Type   |                                                                                             |
| --------- | ------ | ------------------------------------------------------------------------------------------- |
| `alg`     | String | Algorithm used in the JWT. Acceptable values are: `ES256`, `ES384`, `ES512`                 |
| `typ`     | String | Acceptable value: `JWT`                                                                     |
| `kid`     | String | The Key ID used for the JWT. This value must correspond to a key in the DSAP JWKS endpoint. |

#### Decoded JWT Body

```
{
  "iss": "mockClientId",
  "sub": "mockClientId",
  "aud": "https://staging.sign.singpass.gov.sg",
  "iat": 1727665037,
  "exp": 1727665637,
  "jti": "7d647f53-d138-4b51-b1d8-dc69d56ca401"
}
```

| Parameter | Type   | Description                                                                                                                                                                                                                                                |
| --------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iss`     | String | Use the ClientID provided by Singpass                                                                                                                                                                                                                      |
| `sub`     | String | Use the ClientID provided by Singpass                                                                                                                                                                                                                      |
| `aud`     | String | <p>The DSS domain that will be accepting the JWT.<br><br>Value:<br>Staging: <https://staging.sign.singpass.gov.sg><br>Production:<br><https://app.sign.singpass.gov.sg></p>                                                                                |
| `iat`     | Number | A Unix timestamp in seconds indicating the date and time when the JWT was issued.                                                                                                                                                                          |
| `exp`     | Number | A Unix timestamp in seconds indicating the date and time when the JWT will expire. Max expiry allowed is`600` seconds from `iat`                                                                                                                           |
| `jti`     | String | A random string that must be regenerated for **each JWT** to prevent replay attacks. Singpass will cache and reject all duplicate `jti` that is sent within a preset period. As such, DSAPs are advised to use a UUID-v4 value when populating this value. |

**DSAP JWKS Endpoint**

Singpass will attempt to retrieve the public key from the DSAP JWKS endpoint in order to verify the JWT. This endpoint is provided during onboarding and is tied to the DSAP Client ID.

Singpass will only accept JWT signed by EC key. DSAP JWKS should have at least 1 EC key.

#### Sample JWKS Endpoint

```
{
  "keys": [
    {
      "kty": "EC",
      "x": "FuIv2xuY0coivK-MRg01T3_JHUMEtFHdpjj8AuxCPLA",
      "y": "N6pMWRTjPzsBdUTyLrhX7tftfx4IWxvL3z1s9PoxQlE",
      "crv": "P-256",
      "use": "sig",
      "kid": "sign-prod-01"
    }
  ]
}
```

| Parameter | Type   | Description                                                                                                                         |
| --------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `kty`     | String | Key Type Acceptable value: `EC`                                                                                                     |
| `x`       | String | X-Coordinate This is the X-coordinate of the elliptic curve point, which is a component of the public key. It is base64url-encoded. |
| `y`       | String | Y-Coordinate This is the Y-coordinate of the elliptic curve point, which is a component of the public key. It is base64url-encoded. |
| `crv`     | Number | Curve Name                                                                                                                          |
| `use`     | Number | Public Key Use. Acceptable value: `sig`                                                                                             |
| `kid`     | String | Key ID. Singpass will search for the `kid` that was specified in the JWT for verification.                                          |
| `alg`     | String | \[Optional] Algorithm                                                                                                               |

DSAPs are to put the JWT in the request header with the key `X-Dss-Assertion` when calling all DSS APIs.

{% hint style="info" %}
For existing DSAP that are currently using Basic Authentication, DSAPs can continue to use the `Authorization` header when calling DSS APIs. DSS APIs will first detect if `X-Dss-Assertion` exists in the header before falling back to `Authorization`. Existing DSAPs are advised to start preparing for the change to JWT early.
{% endhint %}

### `POST /doc-signing-sessions` <a href="#post-doc-signing-sessions" id="post-doc-signing-sessions"></a>

DSAPs can call this endpoint to start a document signing session. On success, a unique signing reference value - `sign_ref` - is included in the response object. The `sign_ref` is the primary identifier of a signing session used in all exchanges between Singpass and DSAP.

DSAPs have to specify a **client notification token** when initialising a session. Singpass will specify this token in an `Authorization` header for subsequent calls to the DSAP notification webhook endpoints.

{% hint style="info" %}
A document signing session is only valid for a short amount of time. The expiry of a signing session can be found via the `expires_at` response field. When a sign session expires, Singpass will delete it from storage and calls to [<mark style="color:blue;">/doc-signing-sessions/\<sign-ref>/hash</mark>](#post-doc-signing-sessions-less-than-sign-ref-greater-than-hash) will result in a `SIGN_REF_NOT_FOUND` error. Also, any attempts to scan a doc signing QR code or sign a doc hash will fail.&#x20;

Please note that **Singpass will not notify DSAPs** when the signing session expires. It is recommended to make use of the aforementioned expires\_at response field to coordinate the session expiry between Singpass and DSAPs.
{% endhint %}

#### Request and Response Structure

**Sample Curl Request&#x20;**<mark style="color:red;">**(Deprecated)**</mark>

```
$ curl 'https://staging.sign.singpass.gov.sg/api/v1/doc-signing-sessions' -i -u 'client_id:client_secret' -X POST \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{"tenant_id":"some_tenant_id","client_notification_token":"client_notification_token"}' \
    --cert client.crt --key client.key
```

**Sample Curl Request&#x20;**<mark style="color:red;">**(New)**</mark>

```
$ curl 'https://staging.sign.singpass.gov.sg/api/v1/doc-signing-sessions' -i -X POST \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -H 'X-Dss-Assertion: <jwt-assertion>' \
    -d '{"tenant_id":"some_tenant_id","client_notification_token":"client_notification_token"}' \
    --cert client.crt --key client.key
```

#### Sample HTTP Request <mark style="color:red;">(Deprecated)</mark>

```
POST /doc-signing-sessions HTTP/1.1
Accept: application/json
Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
Content-Type: application/json
Host: staging.sign.singpass.gov.sg
Content-Length: 86

{"tenant_id":"some_tenant_id","client_notification_token":"client_notification_token"}
```

#### **Sample HTTP Request&#x20;**<mark style="color:red;">**(New)**</mark>

```
POST /doc-signing-sessions HTTP/1.1
Accept: application/json
X-Dss-Assertion: <jwt-assertion>
Content-Type: application/json
Host: staging.sign.singpass.gov.sg
Content-Length: 86

{"tenant_id":"some_tenant_id","client_notification_token":"client_notification_token"}
```

**Sample Response**

{% code overflow="wrap" %}

```
HTTP/1.1 200 OK
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Cache-Control: no-cache, no-store, must-revalidate
Transfer-Encoding: chunked
Content-Type: application/json
Date: Tue, 24 Sep 2024 02:30:17 GMT
Content-Length: 181

{"sign_ref":"4b1b5241-9594-4e98-b9e3-7f53b73e3fcc","expires_at":1727145019,"qr_code":{"payload":"https://app.singpass.gov.sg/docsign?sign_ref=4b1b5241-9594-4e98-b9e3-7f53b73e3fcc"}}// Some code
```

{% endcode %}

**Request Body**

| Name                        | Type   | Description                                                                                                                                                                                                                                 |
| --------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client_notification_token` | String | <p>A token provided by the DSAP to be used by Singpass when invoking the DSAP’s webhook endpoint.</p><p></p><p>Allowed Characters: Alphanumeric, underscore ( \_ ), dash ( - ), and whitespace </p><p></p><p>Max length: 255 characters</p> |
| `tenant_id`                 | String | <p>The identifier of the tenant of the DSAP client using the doc signing service.</p><p></p><p>Allowed Characters: Alphanumeric, underscore ( \_ ), dash ( - ), and whitespace</p><p></p><p>Max length: 255 characters</p>                  |

{% hint style="info" %}
The value of the `tenant_id` is not the signer's identity. Rather, it is an unique identifier for the DSAP's corporate customers that we can refer to in case of any anomaly detected. If you are not reselling your service to a third party, you can reuse the DSAP name provided during onboarding as the `tenant_id`.

For example: If ABC Bank goes through DSAP to reach DSS, then `tenant_id` must be a unique identifier of ABC Bank. However, if ABC Bank is directly integrated with DSS, then `tenant_id` can be just `ABC Bank`.
{% endhint %}

#### **Request Headers**

| Name              | Description                                                                                                                                                                                           |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization`   | <p><mark style="color:red;">(Deprecated)</mark> </p><p>The DSAP’s client id and client secret in http basic authentication scheme format.</p><p>Not required if DSAP is using JWT to authenticate</p> |
| `X-Dss-Assertion` | <p><mark style="color:red;">(New)</mark> </p><p>JWT issued by the DSAP for authentication.</p>                                                                                                        |

#### **Request Headers**

| Name              | Description                                                                                                                                                                                                    |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization`   | <p><mark style="color:red;">(Deprecated)</mark> </p><p>The DSAP’s client id and client secret in http basic authentication scheme format.</p><p> </p><p>Not required if DSAP is using JWT to authenticate.</p> |
| `X-Dss-Assertion` | <p><mark style="color:red;">(New)</mark> </p><p>JWT issued by the DSAP for authentication.</p>                                                                                                                 |

#### **Response Body**

| Parameter Description | Type   |                                                                                                                                                                                                                                                                           |
| --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sign_ref`            | String | The primary identifier of a signing session used in all exchanges between Singpass and DSAP.                                                                                                                                                                              |
| `qr_code.payload`     | String | The QR payload containing the sign ref in a format understood by SingPass Mobile (SPM). DSAPs are expected to encode and display this as a QR code for SPM users to scan. Refer to the UX Guide provided in the developer package for the QR code display specifications. |
| `expires_at`          | Number | A Unix timestamp in seconds indicating the date and time when the `sign_ref` will expire.                                                                                                                                                                                 |

{% hint style="info" %}
Please note that `sign_ref` should not be treated as a UUID-v4, and the length of the `sign_ref` may vary from time to time.
{% endhint %}

{% hint style="warning" %}
The host of the URL in `qr_code.payload` in staging environment will change to stg-app.singpass.gov.sg after 18 Mar 2025.
{% endhint %}

#### Error Responses

Please refer to [General Error Response.](#general-error-response)

### `POST /doc-signing-sessions/<sign-ref>/hash` <a href="#post-doc-signing-sessions-less-than-sign-ref-greater-than-hash" id="post-doc-signing-sessions-less-than-sign-ref-greater-than-hash"></a>

DSAPs must use this endpoint to send the document hash, document name, and challenge code to Singpass. Singpass will then forward the document information and challenge code to the user for verification and signing.

The DSAP must invoke this API only after it has received the user’s digital signing certificate through its notification webhook endpoint. See **Steps 6, 7 and 8** of the [flow diagram](#document-signing-flow-diagram) for more details about what DSAPs need to do before invoking this endpoint.

#### **Request and Response Structure**

#### **Sample Curl Request&#x20;**<mark style="color:red;">**(Deprecated)**</mark>

```
$ curl 'https://staging.sign.singpass.gov.sg/api/v1/doc-signing-sessions/c6258905-c196-4465-9182-4d95e0163e96/hash' -i -u 'client_id:client_secret' -X POST \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
  "challenge_fqdn" : "signing-partner.gov.sg",
  "doc_name" : "signable-document.pdf",
  "nonce" : "bxUoQ4XiThs0oR19arXcaHpj+lm+i0BPta4YeTXkLXX=",
  "app_launch_url" : "com.example.dsap://home",
  "doc_hash" : "6AC7637DA92C76385F95A92C7617E591A8F6DF8F74F37EF8DB7E25E648E1DB7E",
  "challenge_code" : "1567"
}' \
    --cert client.crt --key client.key
```

#### **Sample Curl Request&#x20;**<mark style="color:red;">**(New)**</mark>

```
$ curl 'https://staging.sign.singpass.gov.sg/api/v1/doc-signing-sessions/c6258905-c196-4465-9182-4d95e0163e96/hash' -i -X POST \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -H 'X-Dss-Assertion: <jwt-assertion>' \
    -d '{
  "challenge_fqdn" : "signing-partner.gov.sg",
  "doc_name" : "signable-document.pdf",
  "nonce" : "bxUoQ4XiThs0oR19arXcaHpj+lm+i0BPta4YeTXkLXX=",
  "app_launch_url" : "com.example.dsap://home",
  "doc_hash" : "6AC7637DA92C76385F95A92C7617E591A8F6DF8F74F37EF8DB7E25E648E1DB7E",
  "challenge_code" : "1567"
}' \
    --cert client.crt --key client.key
```

**Sample HTTP Request&#x20;**<mark style="color:red;">**(Deprecated)**</mark>

```
POST /doc-signing-sessions/c6258905-c196-4465-9182-4d95e0163e96/hash HTTP/1.1
Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
Accept: application/json
Content-Type: application/json
Host: staging.sign.singpass.gov.sg
Content-Length: 309

{
  "challenge_fqdn" : "signing-partner.gov.sg",
  "doc_name" : "signable-document.pdf",
  "nonce" : "bxUoQ4XiThs0oR19arXcaHpj+lm+i0BPta4YeTXkLXX=",
  "app_launch_url" : "com.example.dsap://home",
  "doc_hash" : "6AC7637DA92C76385F95A92C7617E591A8F6DF8F74F37EF8DB7E25E648E1DB7E",
  "challenge_code" : "1567"
}
```

#### **Sample HTTP Request&#x20;**<mark style="color:red;">**(New)**</mark>

```
POST /doc-signing-sessions/c6258905-c196-4465-9182-4d95e0163e96/hash HTTP/1.1
Accept: application/json
X-Dss-Assertion: <jwt-assertion>
Content-Type: application/json
Host: staging.sign.singpass.gov.sg
Content-Length: 309

{
  "challenge_fqdn" : "signing-partner.gov.sg",
  "doc_name" : "signable-document.pdf",
  "nonce" : "bxUoQ4XiThs0oR19arXcaHpj+lm+i0BPta4YeTXkLXX=",
  "app_launch_url" : "com.example.dsap://home",
  "doc_hash" : "6AC7637DA92C76385F95A92C7617E591A8F6DF8F74F37EF8DB7E25E648E1DB7E",
  "challenge_code" : "1567"
}
```

#### **Sample HTTP Response**

```
HTTP/1.1 200 OK
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Date: Tue, 24 Sep 2024 02:30:20 GMT
```

#### Path Parameters

| Parameter  | Description                                                                                 |
| ---------- | ------------------------------------------------------------------------------------------- |
| `sign_ref` | The primary identifier of a signing session used in all exchanges between Singpass and DSAP |

#### **Request Body**

```
{
  "challenge_fqdn" : "signing-partner.gov.sg",
  "doc_name" : "signable-document.pdf",
  "nonce" : "bxUoQ4XiThs0oR19arXcaHpj+lm+i0BPta4YeTXkLXX=",
  "app_launch_url" : "com.example.dsap://home",
  "doc_hash" : "6AC7637DA92C76385F95A92C7617E591A8F6DF8F74F37EF8DB7E25E648E1DB7E",
  "challenge_code" : "1567"
}

```

#### **Request Fields**

<table><thead><tr><th width="207">Parameter</th><th width="154">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>doc_name</code></td><td>String</td><td><p>The name of the document to be signed by the user. Do note that this value will be displayed to the user, so it is recommended to use a human-readable name. </p><p></p><p>Length must be between 1 and 255</p></td></tr><tr><td><code>doc_hash</code></td><td>String</td><td><p>The hash of the document to be signed by the user. Refer to <a href="https://docs.sign.singpass.gov.sg/#document-hash-specifications">Document Hash Specifications</a> for more details. </p><p></p><p>Length must be 64</p></td></tr><tr><td><code>challenge_code</code></td><td>String</td><td><p>A set of characters used by the user to visually verify the signing session between the Singpass App and the DSAP website. </p><p></p><p>Length must be 4. Numerical values are recommended.</p></td></tr><tr><td><code>nonce</code></td><td>String</td><td>Singpass-generated random string sent to the DSAP's webhook endpoint during user certificate notification.</td></tr><tr><td><code>challenge_fqdn</code></td><td>String</td><td><p>The domain name of the webpage displaying the QR code. This will be displayed to the user on the Singpass app. </p><p></p><p>Length must be between 1-255</p></td></tr><tr><td><p><code>app_launch_url</code></p><p></p><p></p></td><td>String</td><td><p>(Optional) This adds the possibility for the user to be redirected back to the provided App Link after they successfully authorize themselves on the Singpass App. The value passed here should be the App Link registered with Apple’s App Store and/or Google’s Play Store. </p><p></p><p>Max length allowed is 255 characters.</p></td></tr></tbody></table>

#### **Request Headers**

<table><thead><tr><th width="375">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>Authorization</code></td><td><p><mark style="color:red;">(Deprecated)</mark> </p><p>The DSAP’s client id and client secret in http basic authentication scheme format. Not required if DSAP is using JWT to authenticate</p></td></tr><tr><td><code>X-Dss-Assertion</code></td><td><p><mark style="color:red;">(New)</mark> </p><p>JWT issued by the DSAP for authentication.</p></td></tr></tbody></table>

**Response Body**

There is no body in the response for successful API call.

#### **Error Response**

If Singpass fails to find the requested `sign_ref` (e.g. due to expiry), this API will return a `HTTP 400` error, and an accompanying `SIGN_REF_NOT_FOUND`error code in the response body.

For other errors, please refer to [General Error Response.](#general-error-response)

### `GET /.well-known/keys.json`

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. For more information, please refer to [Caching and key rotation.](#caching-and-key-rotation)

#### Request and Response Structure

#### **Sample Curl Request**

```
$ curl 'https://static.staging.sign.singpass.gov.sg/.well-known/keys.json' -i -X GET
```

#### **Sample HTTP Request**

```
GET /.well-known/keys.json HTTP/1.1
Host: static.staging.sign.singpass.gov.sg
```

#### **Sample HTTP Response**

```
HTTP/1.1 200 OK
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Cache-Control: max-age=21600, must-revalidate, no-transform, public
Content-Type: application/json
Content-Length: 436
Date: Tue, 24 Sep 2024 02:30:00 GMT

{
  "keys" : [ {
    "kty" : "EC",
    "use" : "sig",
    "crv" : "P-256",
    "kid" : "ndi_dss_signer_02",
    "x" : "ObZ0BvgODnkQqMK5OjBWn5V6DCWlEhICwC7UuXxs-Vw",
    "y" : "c9CjVC779_rvOWMwbh4cE4Jx3bcfKe_20WVDtZYc_Ns"
  }, {
    "kty" : "EC",
    "use" : "sig",
    "crv" : "P-256",
    "kid" : "ndi_dss_signer",
    "x" : "dL8zGIrEMDfwlMxsd_uYqNbjq6PUHaswudG5lMJlcyI",
    "y" : "f2mU8Q2eE1pUsN37ktEHTf3n0u1u_2N7VRRus-3u4Z0"
  } ]
}
```

#### Request Body

There is no request body for this API call.

#### **Response Body**

```
{
  "keys" : [ {
    "kty" : "EC",
    "use" : "sig",
    "crv" : "P-256",
    "kid" : "ndi_dss_signer_02",
    "x" : "ObZ0BvgODnkQqMK5OjBWn5V6DCWlEhICwC7UuXxs-Vw",
    "y" : "c9CjVC779_rvOWMwbh4cE4Jx3bcfKe_20WVDtZYc_Ns"
  }, {
    "kty" : "EC",
    "use" : "sig",
    "crv" : "P-256",
    "kid" : "ndi_dss_signer",
    "x" : "dL8zGIrEMDfwlMxsd_uYqNbjq6PUHaswudG5lMJlcyI",
    "y" : "f2mU8Q2eE1pUsN37ktEHTf3n0u1u_2N7VRRus-3u4Z0"
  } ]
}
```

#### Caching and Key Rotation

{% hint style="warning" %}
Responses from this endpoint, or individual keys from inside the JWKS can and should be cached for at least 1 hour, and NOT retrieved for each JWT validation. Cache-Control headers on the response indicate a possible policy.
{% endhint %}

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.

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.

## DSAP Notification Webhook Endpoint <a href="#dsap-notification-webhook-endpoint" id="dsap-notification-webhook-endpoint"></a>

### Usage <a href="#usage" id="usage"></a>

Singpass will inform DSAP of various events that happen during the document signing process. To facilitate this, DSAPs must implement and expose a webhook endpoint to accept information from Singpass.

{% hint style="info" %}
The DSAP’s webhook is the main and only mechanism for Singpass to notify the DSAP of the events that happen in the signing flow. DSAPs cannot poll or query Singpass to get the state of the user’s doc signing session. See the Session expiry section for details on how session expiry should be handled.
{% endhint %}

Singpass will call the webhook endpoint to inform of the following events:

1. User certificate notification - To send the user’s certificate (See Step 6 of flow diagram).
2. Document hash signature notification - To send the user’s document hash signature (See Step 14 of flow diagram).
3. Webhook error notification - To inform DSAP of any errors encountered during the webhook calls above.

{% hint style="warning" %}
Please note that for all calls to the webhook endpoint, DSAPs are expected to respond `HTTP 200` to the webhook calls immediately, before any significant processing is performed on the request. This is to keep low latencies on the user-facing APIs that are dependent on the webhook calls and give a better overall experience to Singpass users.
{% endhint %}

### Specifications

* The webhook must accept the `POST` HTTP method.
* It should expect a [JSON Web Tokens (JWT)](https://tools.ietf.org/html/rfc7519) in the request body.
* It should authenticate incoming calls using the [Bearer authentication scheme](https://tools.ietf.org/html/rfc6750), accepting only the `client_notification_token` given to Singpass during session initialisation.
* For non-repudiation purposes, Singpass will sign all JWTs sent to DSAPs webhook with its signing key. DSAPs can retrieve the corresponding public keys for verification from [/.well-known/keys.json.](#get-.well-known-keys.json)
* Singpass will perform automated retries of webhook requests if it encounters timeouts and on certain errors. See the webhook Retry Settings for more details.
* It is sufficient for DSAPs to respond with an HTTP status code without any response body for this endpoint.

### IP Address Whitelisting

Singpass will call your webhook endpoints from these IP addresses, which you may choose to whitelist.

| Staging       | Production    |
| ------------- | ------------- |
| 54.169.20.186 | 18.143.229.39 |
|               | 54.179.76.90  |
|               | 3.0.39.45     |

### Request and Response Structure <a href="#request-and-response-structure-3" id="request-and-response-structure-3"></a>

#### **Sample Curl Request**

{% code overflow="wrap" %}

```
$ curl 'https://<dsap_domain_url>/dsap/webhook' -i -X POST \
    -H 'Authorization: Bearer <client_notification_token>' \
    -H 'Content-Type: application/json' \
    -d '{"token":"eyJraWQiOiJuZGlfZHNzX3NpZ25lciIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzaWduX3JlZiI6IjU2MTYzZTg5LWEyMzItNDUwMi1hNDRmLWUyMjI1MTJiYTFhZCIsInJlcXVlc3RfdHlwZSI6InVzZXJfY2VydCIsImV4cCI6MTcyNzE0NTE1MiwidXNlcl9jZXJ0IjoiTUlJQnJUQ0NBVEtnQXdJQkFnSUNBK2N3Q2dZSUtvWkl6ajBFQXdNd05URUxNQWtHQTFVRUJoTUNVMGN4RERBS0JnTlZCQW9NQTA1RVNURVlNQllHQTFVRUF3d1BkR1Z6ZEVCdVpHa3VaMjkyTG5Obk1CNFhEVEl3TURZd016QTNNalF5TjFvWERUSXdNRFl3TXpBM01qWXdOMW93VERFdE1Dc0dBMVVFQlJNa09HRmtPREExWW1VdE56Z3pOQzAwWWpZNExXRmxZek10Tm1NNE5UUTNObVpsWWpFeE1Sc3dHUVlEVlFRRERCSlRNREF3TURBd01Ea2dTbTlvYmlCRWIyVXdkakFRQmdjcWhrak9QUUlCQmdVcmdRUUFJZ05pQUFSb2FRWUVTQWpaUzBISnJwY1g1bWpRZlFzT0RaQ0s1WW1ybFdJejFyaXp3dzRBWEQ5bzRkdFJVZHBNOStGQWtlM2NreFlpWmM5SzJoYXZZdVRLLy9kM09KRzlFVHlyZ0VsVXRoV1c2R2FCZEZzV1pnRHMvenMzRkhyMFJvTThYLzB3Q2dZSUtvWkl6ajBFQXdNRGFRQXdaZ0l4QUw5WUl1M3hZMnY5YndiL2NoUWdPN0p6YnJxOGd0aTJOVmFoc0Q3Sk5kOUErOFJKcmR5QlRGZlpSMDA0elYzNk9RSXhBTzVZVHFYUWdydys1UFpXWjZSYWV3VkRKbjdEeXdYUUpiZXk3WnJ5MjdlZDdoeHNZYVQ5QlBBNElvNll5MmhHS1E9PSIsImlhdCI6MTcyNzE0NTAzMiwibm9uY2UiOiI5NjBiYzY5Yy1lY2M5LTRlOWEtODY4Yy04OTkyNTgwYjUyNTcifQ.ERUp7x8yEYZkdJEDU3z_0IZNYb0MdKknaoYYZXjQ8X4Dh29qtAP39W4N3dGJu_vvhYbtSjXX-JgXEINsMj8rtkkULZWsRXN8GTPalFTFc6l00_Kx09SLiha6Lc-UevED"}'
```

{% endcode %}

#### Sample HTTP Request

{% code overflow="wrap" %}

```
POST /dsap/webhook HTTP/1.1
Authorization: Bearer <client_notification_token>
Content-Type: application/json
Host: <dsap_domain_url>:443
Content-Length: 1215

{"token":"eyJraWQiOiJuZGlfZHNzX3NpZ25lciIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzaWduX3JlZiI6IjU2MTYzZTg5LWEyMzItNDUwMi1hNDRmLWUyMjI1MTJiYTFhZCIsInJlcXVlc3RfdHlwZSI6InVzZXJfY2VydCIsImV4cCI6MTcyNzE0NTE1MiwidXNlcl9jZXJ0IjoiTUlJQnJUQ0NBVEtnQXdJQkFnSUNBK2N3Q2dZSUtvWkl6ajBFQXdNd05URUxNQWtHQTFVRUJoTUNVMGN4RERBS0JnTlZCQW9NQTA1RVNURVlNQllHQTFVRUF3d1BkR1Z6ZEVCdVpHa3VaMjkyTG5Obk1CNFhEVEl3TURZd016QTNNalF5TjFvWERUSXdNRFl3TXpBM01qWXdOMW93VERFdE1Dc0dBMVVFQlJNa09HRmtPREExWW1VdE56Z3pOQzAwWWpZNExXRmxZek10Tm1NNE5UUTNObVpsWWpFeE1Sc3dHUVlEVlFRRERCSlRNREF3TURBd01Ea2dTbTlvYmlCRWIyVXdkakFRQmdjcWhrak9QUUlCQmdVcmdRUUFJZ05pQUFSb2FRWUVTQWpaUzBISnJwY1g1bWpRZlFzT0RaQ0s1WW1ybFdJejFyaXp3dzRBWEQ5bzRkdFJVZHBNOStGQWtlM2NreFlpWmM5SzJoYXZZdVRLLy9kM09KRzlFVHlyZ0VsVXRoV1c2R2FCZEZzV1pnRHMvenMzRkhyMFJvTThYLzB3Q2dZSUtvWkl6ajBFQXdNRGFRQXdaZ0l4QUw5WUl1M3hZMnY5YndiL2NoUWdPN0p6YnJxOGd0aTJOVmFoc0Q3Sk5kOUErOFJKcmR5QlRGZlpSMDA0elYzNk9RSXhBTzVZVHFYUWdydys1UFpXWjZSYWV3VkRKbjdEeXdYUUpiZXk3WnJ5MjdlZDdoeHNZYVQ5QlBBNElvNll5MmhHS1E9PSIsImlhdCI6MTcyNzE0NTAzMiwibm9uY2UiOiI5NjBiYzY5Yy1lY2M5LTRlOWEtODY4Yy04OTkyNTgwYjUyNTcifQ.ERUp7x8yEYZkdJEDU3z_0IZNYb0MdKknaoYYZXjQ8X4Dh29qtAP39W4N3dGJu_vvhYbtSjXX-JgXEINsMj8rtkkULZWsRXN8GTPalFTFc6l00_Kx09SLiha6Lc-UevED"}
```

{% endcode %}

#### Sample HTTP Response

```
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2024 02:30:32 GMT
Connection: close
```

#### **Request Body**

{% code overflow="wrap" %}

```
{"token":"eyJraWQiOiJuZGlfZHNzX3NpZ25lciIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzaWduX3JlZiI6IjU2MTYzZTg5LWEyMzItNDUwMi1hNDRmLWUyMjI1MTJiYTFhZCIsInJlcXVlc3RfdHlwZSI6InVzZXJfY2VydCIsImV4cCI6MTcyNzE0NTE1MiwidXNlcl9jZXJ0IjoiTUlJQnJUQ0NBVEtnQXdJQkFnSUNBK2N3Q2dZSUtvWkl6ajBFQXdNd05URUxNQWtHQTFVRUJoTUNVMGN4RERBS0JnTlZCQW9NQTA1RVNURVlNQllHQTFVRUF3d1BkR1Z6ZEVCdVpHa3VaMjkyTG5Obk1CNFhEVEl3TURZd016QTNNalF5TjFvWERUSXdNRFl3TXpBM01qWXdOMW93VERFdE1Dc0dBMVVFQlJNa09HRmtPREExWW1VdE56Z3pOQzAwWWpZNExXRmxZek10Tm1NNE5UUTNObVpsWWpFeE1Sc3dHUVlEVlFRRERCSlRNREF3TURBd01Ea2dTbTlvYmlCRWIyVXdkakFRQmdjcWhrak9QUUlCQmdVcmdRUUFJZ05pQUFSb2FRWUVTQWpaUzBISnJwY1g1bWpRZlFzT0RaQ0s1WW1ybFdJejFyaXp3dzRBWEQ5bzRkdFJVZHBNOStGQWtlM2NreFlpWmM5SzJoYXZZdVRLLy9kM09KRzlFVHlyZ0VsVXRoV1c2R2FCZEZzV1pnRHMvenMzRkhyMFJvTThYLzB3Q2dZSUtvWkl6ajBFQXdNRGFRQXdaZ0l4QUw5WUl1M3hZMnY5YndiL2NoUWdPN0p6YnJxOGd0aTJOVmFoc0Q3Sk5kOUErOFJKcmR5QlRGZlpSMDA0elYzNk9RSXhBTzVZVHFYUWdydys1UFpXWjZSYWV3VkRKbjdEeXdYUUpiZXk3WnJ5MjdlZDdoeHNZYVQ5QlBBNElvNll5MmhHS1E9PSIsImlhdCI6MTcyNzE0NTAzMiwibm9uY2UiOiI5NjBiYzY5Yy1lY2M5LTRlOWEtODY4Yy04OTkyNTgwYjUyNTcifQ.ERUp7x8yEYZkdJEDU3z_0IZNYb0MdKknaoYYZXjQ8X4Dh29qtAP39W4N3dGJu_vvhYbtSjXX-JgXEINsMj8rtkkULZWsRXN8GTPalFTFc6l00_Kx09SLiha6Lc-UevED"}
```

{% endcode %}

#### **Request Fields**

| Name    | Type   | Description                                                                                                            |
| ------- | ------ | ---------------------------------------------------------------------------------------------------------------------- |
| `token` | String | The signed JWT containing standard and custom claims that describe a user or error event of a digital signing session. |

#### **Request Headers**

| Name            | Description                                                                                                                                              |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization` | Bearer token authentication credentials. The token value is equal to the `client_notification_token` provided by the DSAP during session initialization. |

#### **Response Body**

There is no body in the response for this API call.

#### Request `token` structure

As mentioned previously, the request body `token` field is a JWT signed by Singpass, which can be verified by DSAPs.

#### **JWT Header (User Certificate)**

```
{
  "kid" : "ndi_dss_signer",
  "typ" : "JWT",
  "alg" : "ES256"
}
```

#### **JWT Claims (User Certificate)**

{% code overflow="wrap" %}

```
{
  "sign_ref" : "56163e89-a232-4502-a44f-e222512ba1ad",
  "request_type" : "user_cert",
  "exp" : 1727145152,
  "user_cert" : "MIIBrTCCATKgAwIBAgICA+cwCgYIKoZIzj0EAwMwNTELMAkGA1UEBhMCU0cxDDAKBgNVBAoMA05ESTEYMBYGA1UEAwwPdGVzdEBuZGkuZ292LnNnMB4XDTIwMDYwMzA3MjQyN1oXDTIwMDYwMzA3MjYwN1owTDEtMCsGA1UEBRMkOGFkODA1YmUtNzgzNC00YjY4LWFlYzMtNmM4NTQ3NmZlYjExMRswGQYDVQQDDBJTMDAwMDAwMDkgSm9obiBEb2UwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARoaQYESAjZS0HJrpcX5mjQfQsODZCK5YmrlWIz1rizww4AXD9o4dtRUdpM9+FAke3ckxYiZc9K2havYuTK//d3OJG9ETyrgElUthWW6GaBdFsWZgDs/zs3FHr0RoM8X/0wCgYIKoZIzj0EAwMDaQAwZgIxAL9YIu3xY2v9bwb/chQgO7Jzbrq8gti2NVahsD7JNd9A+8RJrdyBTFfZR004zV36OQIxAO5YTqXQgrw+5PZWZ6RaewVDJn7DywXQJbey7Zry27ed7hxsYaT9BPA4Io6Yy2hGKQ==",
  "iat" : 1727145032,
  "nonce" : "960bc69c-ecc9-4e9a-868c-8992580b5257"
}
```

{% endcode %}

#### **Description of Claims (User Certificate)**

<table><thead><tr><th width="251">Parameter</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><p><code>sign_ref</code></p><p></p></td><td>String</td><td>The primary identifier of a signing session used in all exchanges between Singpass and DSAP.</td></tr><tr><td><code>user_cert</code></td><td>String</td><td>The user's digital signing certificate in base64-encoded DER format.</td></tr><tr><td><code>iat</code></td><td>Number</td><td>A Unix timestamp in seconds indicating the date and time when the JWT was issued.</td></tr><tr><td><code>exp</code></td><td>Number</td><td>A Unix timestamp in seconds indicating the date and time when the JWT will expire.</td></tr><tr><td><code>nonce</code></td><td>String</td><td>A randomly generated string used to associate this user certificate notification to a document hash callback . This nonce must be included in the subsequent document hash callback request from DSAP.</td></tr><tr><td><code>request_type</code></td><td>String</td><td>Value to indicate the type of the request, value is <code>user_cert</code>.</td></tr></tbody></table>

#### **Description of Claims (Document Hash Signature)**

| Parameter            | Type   | Description                                                                                                                                                          |
| -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sign_ref`           | String | The primary identifier of a signing session used in all exchanges between Singpass and DSAP.                                                                         |
| `doc_hash`           | String | The hash of the document to be signed by the user. Refer to [Document Hash Specifications](#document-hash-specifications) for more details.                          |
| `iat`                | Number | A Unix timestamp in seconds indicating the date and time when the JWT was issued.                                                                                    |
| `exp`                | Number | A Unix timestamp in seconds indicating the date and time when the JWT will expire.                                                                                   |
| `doc_hash_signature` | String | The signature of the document hash, signed by the user. Refer to [Document Hash Signature Specifications](#document-hash-signature-specifications) for more details. |
| `request_type`       | String | Value to indicate the type of the request, value is `signed_doc_hash`.                                                                                               |

#### **Description of Claims (Error)**

| Parameter           | Type   | Description                                                                                                                                 |
| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `sign_ref`          | String | The primary identifier of a signing session used in all exchanges between Singpass and DSAP.                                                |
| `error`             | String | Error code representing an error, e.g. `CLIENT_NOTIFICATION_FAILED`                                                                         |
| `iat`               | Number | A Unix timestamp in seconds indicating the date and time when the JWT was issued.                                                           |
| `exp`               | Number | A Unix timestamp in seconds indicating the date and time when the JWT will expire.                                                          |
| `error_description` | String | Returns human readable general information about the reason for the error.                                                                  |
| `request_type`      | String | Value to indicate the type of the request, value is `error`.                                                                                |
| `ndi_request_id`    | String | <p></p><p>A randomly generated ID that can be used to correlate requests between Singpass and DSAP. This ID is unique per user request.</p> |

### Retry Settings <a href="#retry-settings" id="retry-settings"></a>

Singpass will retry on any connection errors, client timeout and the following http response codes returned from the DSAP webhook endpoint:

* 502 - BAD\_GATEWAY
* 503 - SERVICE\_UNAVAILABLE
* 504 - GATEWAY\_TIMEOUT

Singpass is using the following configuration for retry strategy:

```
Total timeout across all attempts: 5s
Per try timeout: 2s
Min back off: 500ms
Max attempts: 3
```

### Error Response

The DSAP webhook API must follow RESTful conventions and communicate errors by returning a proper error response and with a corresponding HTTP status code, following the structure indicated below. All fields are mandatory.

#### Response Fields

| Parameter           | Type   | Description                                                                                                                                 |
| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                | String | A randomly generated ID that can be used to correlate requests between Singpass and DSAP. This ID is unique per user request.               |
| `error`             | String | Error code representing an error. Must be one of the values from the list of [v](#valid-error-codes)[alid error codes.](#valid-error-codes) |
| `error_description` | String | Returns human readable general information about the reason for the error.                                                                  |

#### **Sample Error Response**

```
{
  "id" : "1234",
  "error" : "SIGN_REF_NOT_FOUND",
  "error_description" : "The requested sign ref does not exist."
}
```

### Valid Error Codes <a href="#valid-error-codes" id="valid-error-codes"></a>

| Error                | Expected Http Status |                                                                                                                                                                                        |
| -------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SIGN_REF_NOT_FOUND` | 400                  | To be returned if the sign ref in the request is unknown or expired.                                                                                                                   |
| `INVALID_REQUEST`    | 400                  | A catch all indicating an invalid request parameter from DSS. Eg. invalid client notification token, invalid user cert, invalid doc hash signature, etc.                               |
| `SERVER_ERROR`       | 5XX                  | <p></p><p>A catch all for any other errors that occurred in the DSAP.</p><p></p><p>Singpass will retry on 502, 503 and 504. Refer to <a href="#retry-settings">Retry Settings.</a></p> |

## Other DSAP Requirements <a href="#other-dsap-requirements" id="other-dsap-requirements"></a>

DSAPs are expected to verify the validity of the user’s certificate status against National Certificate Authority (NCA).

{% hint style="info" %}
Singpass only validates the expiry, signature and signer of the certificate against the NCA Root and Intemediary CAs.
{% endhint %}

* DSAPs must use [Online Certificate Status Protocol (OCSP)](https://tools.ietf.org/html/rfc6960) to do the status verification against NCA. The OCSP response must be embedded in the signed document.
* DSAPs shall not send unknown OCSP requests (e.g. unknown OCSP certificate serial number or unknown CA Hash name).
* Cryptographic algorithms: DSAP shall ensure that the default cryptographic algorithms used in its systems and during the Digital Signing Process are as follows:
  * Digital Signature Algorithms: ECDSA (key size with 256 bits or more);
  * ECC Curves: NIST P-256, P-384, P-521;
  * Hash Algorithms: SHA-2 (256/384/512).
* The [Certificate Revocation List (CRL) file](https://www.nca.gov.sg/SNICA-G1.crl) size is **large** so we do not recommend using [CRL](https://tools.ietf.org/html/rfc5280#section-6.3) to verify the cert status to avoid large signed document file sizes.

## Document Hash Specifications

DSAPs are expected to use the received user cert and generate a document hash that can later be digitally signed (locally) to form a [PAdES-LTV/LTA](https://www.etsi.org/deliver/etsi_ts/102700_102799/10277804/01.01.02_60/ts_10277804v010102p.pdf) compliant signature.

The document hash should be **SHA256 hashed** and **hex-encoded** before sending to Singpass.

## Document Hash Signature Specifications <a href="#document-hash-signature-specifications" id="document-hash-signature-specifications"></a>

The document hash is locally signed by the user’s digital identity, verified by Singpass and then sent to the DSAP.

The signature follows the [**ASN.1/DER format**](https://www.cryptosys.net/pki/manpki/pki_ecchexformat.html) and will be **hex-encoded**.

{% hint style="info" %}
The document hash is not re-hashed during signing.
{% endhint %}

DSAPs are expected to then generate a [PAdES-LTV/LTA](https://www.etsi.org/deliver/etsi_ts/102700_102799/10277804/01.01.02_60/ts_10277804v010102p.pdf) compliant signature using this.

## UI/UX Specifications

DSAPs should follow the UX Guide provided in the developer package given during onboarding when displaying the QR code and/or challenge code to the users on their webpage.

### QR Code deeplinking specifications <a href="#qr-code-deeplinking-specifications" id="qr-code-deeplinking-specifications"></a>

To support mobile users, DSAPs are also required to render the QR code with the mobile app links (deep links) following the format below.

**For production:**

<table data-full-width="false"><thead><tr><th width="170">Platform</th><th width="580"></th></tr></thead><tbody><tr><td>Android</td><td><code>intent://app.singpass.gov.sg/docsign?sign_ref=&#x3C;sign-ref>#Intent;scheme=https;package=sg.ndi.sp;S.browser_fallback_url=https://app.singpass.gov.sg/docsign;end</code></td></tr><tr><td>iOS</td><td><code>https://app.singpass.gov.sg/docsign?sign_ref=&#x3C;sign-ref></code></td></tr></tbody></table>

**For staging:**

<table data-full-width="false"><thead><tr><th width="170">Platform</th><th width="580"></th></tr></thead><tbody><tr><td>Android</td><td><code>intent://stg-app.singpass.gov.sg/docsign?sign_ref=&#x3C;sign-ref>#Intent;scheme=https;package=sg.ndi.sp.dev;S.browser_fallback_url=https://stg-app.singpass.gov.sg/docsign;end</code></td></tr><tr><td>iOS</td><td><code>https://stg-app.singpass.gov.sg/docsign?sign_ref=&#x3C;sign-ref></code></td></tr></tbody></table>

{% hint style="danger" %}
The new staging domain `stg-app.singpass.gov.sg` will only take effect from 18 Mar 2025. In the meantime, RP must use `app.singpass.gov.sg` for both staging and production.
{% endhint %}

## Frequently Asked Questions (FAQs) <a href="#frequently-asked-questions-faqs" id="frequently-asked-questions-faqs"></a>

**Question: Since the** [<mark style="color:blue;">**user cert notification**</mark>](#dsap-notification-webhook-endpoint) **webhook is defined as asynchronous and non-blocking, are there any constraints on when a DSAP should invoke the** [<mark style="color:blue;">**/doc-signing-sessions/\<sign-ref>/hash endpoint**</mark>](#post-doc-signing-sessions-less-than-sign-ref-greater-than-hash)**?**

Answer: Yes. The agreed average response time between the start of the [<mark style="color:blue;">user cert notification</mark>](#dsap-notification-webhook-endpoint) webhook and the invocation of the hash endpoint must be 8 seconds. This 8-second interval correlates to the period that an end-user has to wait after scanning the QR (till he/she receives a "Signing Challenge"). While waiting for the "Signing Challenge" (which is provided by a DSAP), the end-user will encounter a loading/ spinner screen.<br>

**Question: Are DSAPs allowed to cache an end-user’s public X.509 Certificate beyond the TTL of a signing-session (`sign_ref`) to cater for better user experience?**

Answer: Although it is technically possible for DSAPs to cache an end-user’s public certificate beyond the TTL of a signing-session (`sign_ref`), Singpass does not condone such a practice as it infringes Data Protection Policies. i.e. Lack of end-user’s consent in providing his/her personal information to a third-party site/application (<https://www.singpass.gov.sg/singpass/common/privacystatement>).

The [<mark style="color:blue;">/doc-signing-sessions/\<sign-ref>/hash endpoint</mark>](#post-doc-signing-sessions-less-than-sign-ref-greater-than-hash) will proceed to reject any requests of such nature if an invalid signing-reference ID (`sign_ref`) is provided. Repeated attempts to guess a signing-reference ID (sign\_ref) will be logged and recognised by Singpass as a malicious security event.
