Document Signing V1

Last Updated: 2 June 2025

Changelog

Date
Changes

30 Sep 2024

11 Nov 2024

Added IP addresses for whitelisting

5 Mar 2025

Update to QR Code deeplinking specifications to support new staging app

27 May 2025

2 June 2025

Updated to indicate that V3 is released; onboarding for v1 no longer supported

Introduction

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

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

Document signing flow

Staging and Production URLs

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

  • SSL

  • Basic Authentication (Deprecated)

  • JWT (New)

Production

  • SSL

  • Basic Authentication (Deprecated)

  • JWT (New)

JWKS URLs

DSAP Server Certificate

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. Please note that your cert should not be a self-signed certificate.

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

Requirement
RSA Certs
ECC Certs

Required Public Key dimensions

>= 2048 bits

>= 3072 bits (recommended)

>= 256-bit based on curves P-256, P-384 or P-521 (NIST curves, aka secp256r1, secp384r1, secp521r1 respectively)

X.509 v3 extension KeyUsage

digitalSignature, keyEncipherment

digitalSignature

X509 v3 extension ExtendedKeyUsage

serverAuth

serverAuth

General Error Response

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.

HTTP Status Code
Description

4XX

Errors caused by API consumer. You can expect codes such as 400, 401, 403, 404 etc if incorrect requests are made to APIs.

Example: 400: Invalid/missing request arguments

5XX

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.

Example: 500: Internal Server Error due to some kind of programming error.

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

Path
Type
Description

id

String

The unique identifier for this error/request. Please log this identifier for support and debugging purposes.

trace_id

String

(Optional) An auxiliary id for request correlation across services. Please also log this identifier for operational support and debugging purposes.

error

String

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.

error_description

String

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.

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

Authorization header value is invalid.

MISSING_AUTHORIZATION

Authorization header was required but not found.

ARGUMENTS_NOT_VALID

Some request parameters are invalid.

SIGN_REF_NOT_FOUND

Requested sign ref is invalid or has already expired.

MULTIPLE_ASSERTION_HEADER

Multiple DSS assertion headers.

ASSERTION_NOT_VALID

DSS assertion header is invalid.

JWKS_URL_NOT_FOUND

DSAP JWKS URL is not found.

CLIENT_JWKS_ERROR

Unable to fetch DSAP JWKS or DSAP JWKS is invalid.

Document Signing endpoints invoked by DSAP

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)

For all DSS APIs, DSAPs will need to authenticate themselves using their base64-encoded client_id and client_secret via the basic authentication scheme in the following format:

Authorization: Basic <client_id>:<client_secret>

Authenticating via JSON Web Token

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

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InJwLXRlc3QtMDEifQ.eyJpc3MiOiJtb2NrQ2xpZW50SWQiLCJzdWIiOiJtb2NrQ2xpZW50SWQiLCJhdWQiOiJodHRwczovL3N0YWdpbmcuc2lnbi5zaW5ncGFzcy5nb3Yuc2ciLCJpYXQiOjE3Mjc2NjUwMzcsImV4cCI6MTcyNzY2NTYzNywianRpIjoiN2Q2NDdmNTMtZDEzOC00YjUxLWIxZDgtZGM2OWQ1NmNhNDAxIn0.iF9WH09CW1zhOhaSH37Faaw6kw7dbUMx2yuIAW9tD7p50ixUWIBTswvwxkutIPFY4zGFxcjrV4USwBfxpe28RA

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

The DSS domain that will be accepting the JWT.

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 is600 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.

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.

POST /doc-signing-sessions

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.

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 /doc-signing-sessions/<sign-ref>/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.

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.

Request and Response Structure

Sample Curl Request (Deprecated)

$ 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 (New)

$ 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 (Deprecated)

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 (New)

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

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

Request Body

Name
Type
Description

client_notification_token

String

A token provided by the DSAP to be used by Singpass when invoking the DSAP’s webhook endpoint.

Allowed Characters: Alphanumeric, underscore ( _ ), dash ( - ), and whitespace

Max length: 255 characters

tenant_id

String

The identifier of the tenant of the DSAP client using the doc signing service.

Allowed Characters: Alphanumeric, underscore ( _ ), dash ( - ), and whitespace

Max length: 255 characters

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.

Request Headers

Name
Description

Authorization

(Deprecated)

The DSAP’s client id and client secret in http basic authentication scheme format.

Not required if DSAP is using JWT to authenticate

X-Dss-Assertion

(New)

JWT issued by the DSAP for authentication.

Request Headers

Name
Description

Authorization

(Deprecated)

The DSAP’s client id and client secret in http basic authentication scheme format.

Not required if DSAP is using JWT to authenticate.

X-Dss-Assertion

(New)

JWT issued by the DSAP for authentication.

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.

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.

Error Responses

Please refer to General Error Response.

POST /doc-signing-sessions/<sign-ref>/hash

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 for more details about what DSAPs need to do before invoking this endpoint.

Request and Response Structure

Sample Curl Request (Deprecated)

$ 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 (New)

$ 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 (Deprecated)

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 (New)

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

Parameter
Type
Description

doc_name

String

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.

Length must be between 1 and 255

doc_hash

String

The hash of the document to be signed by the user. Refer to Document Hash Specifications for more details.

Length must be 64

challenge_code

String

A set of characters used by the user to visually verify the signing session between the Singpass App and the DSAP website.

Length must be 4. Numerical values are recommended.

nonce

String

Singpass-generated random string sent to the DSAP's webhook endpoint during user certificate notification.

challenge_fqdn

String

The domain name of the webpage displaying the QR code. This will be displayed to the user on the Singpass app.

Length must be between 1-255

app_launch_url

String

(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.

Max length allowed is 255 characters.

Request Headers

Name
Description

Authorization

(Deprecated)

The DSAP’s client id and client secret in http basic authentication scheme format. Not required if DSAP is using JWT to authenticate

X-Dss-Assertion

(New)

JWT issued by the DSAP for authentication.

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_FOUNDerror code in the response body.

For other errors, please refer to 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.

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.

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

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

Usage

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.

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.

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.

Specifications

  • The webhook must accept the POST HTTP method.

  • It should expect a JSON Web Tokens (JWT) in the request body.

  • It should authenticate incoming calls using the Bearer authentication scheme, 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.

  • 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

Sample Curl Request

$ 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"}'

Sample HTTP Request

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"}

Sample HTTP Response

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

Request Body

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

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)

{
  "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"
}

Description of Claims (User Certificate)

Parameter
Type
Description

sign_ref

String

The primary identifier of a signing session used in all exchanges between Singpass and DSAP.

user_cert

String

The user's digital signing certificate in base64-encoded DER format.

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.

nonce

String

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.

request_type

String

Value to indicate the type of the request, value is user_cert.

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 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 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

A randomly generated ID that can be used to correlate requests between Singpass and DSAP. This ID is unique per user request.

Retry Settings

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 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

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

A catch all for any other errors that occurred in the DSAP.

Singpass will retry on 502, 503 and 504. Refer to Retry Settings.

Other DSAP Requirements

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

Singpass only validates the expiry, signature and signer of the certificate against the NCA Root and Intemediary CAs.

  • DSAPs must use Online Certificate Status Protocol (OCSP) 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 size is large so we do not recommend using CRL 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 compliant signature.

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

Document Hash Signature Specifications

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 and will be hex-encoded.

The document hash is not re-hashed during signing.

DSAPs are expected to then generate a PAdES-LTV/LTA 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

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:

Platform

Android

intent://app.singpass.gov.sg/docsign?sign_ref=<sign-ref>#Intent;scheme=https;package=sg.ndi.sp;S.browser_fallback_url=https://app.singpass.gov.sg/docsign;end

iOS

https://app.singpass.gov.sg/docsign?sign_ref=<sign-ref>

For staging:

Platform

Android

intent://stg-app.singpass.gov.sg/docsign?sign_ref=<sign-ref>#Intent;scheme=https;package=sg.ndi.sp.dev;S.browser_fallback_url=https://stg-app.singpass.gov.sg/docsign;end

iOS

https://stg-app.singpass.gov.sg/docsign?sign_ref=<sign-ref>

Frequently Asked Questions (FAQs)

Question: Since the user cert notification webhook is defined as asynchronous and non-blocking, are there any constraints on when a DSAP should invoke the /doc-signing-sessions/<sign-ref>/hash endpoint?

Answer: Yes. The agreed average response time between the start of the user cert notification 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.

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 /doc-signing-sessions/<sign-ref>/hash endpoint 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.

Last updated

Was this helpful?