# Init Transaction Signing

RPs should invoke this function to start a transaction signing session with Singpass. This is the only function that needs to be invoked by the RP for every transaction signing session.

{% code overflow="wrap" %}

```js
initTxnSigning(DOMElementID, clientParams, transactionParamsSupplier, onError, additionalOptions)
```

{% endcode %}

Behind the scenes, Singpass JS will coordinate the required backend API calls to create a new session and renders the UI screens accordingly as users progress through the signing process.

Firstly, Singpass JS will perform pre-flight checks to validate the function method parameters and ensure that the end-user’s browser/user-agent is supported by Singpass JS.

Next, Singpass JS will call the `/txn-signing-sessions` endpoint that is exposed on the Singpass backend to initiate the transaction signing flow.

Upon successful invocation of this function and the session initiation by Singpass, a QR code will be generated and displayed for the user to scan with the Singpass app (SPM) and proceed with the transaction.

At the same time, Singpass JS will call the `/status` endpoint, which is a long-polling connection to the Singpass backend. This endpoint reacts to user actions in the SPM app, i.e. "user scanned" and "user authorised".

{% hint style="info" %}
Wrap the invocation of `initTxnSigning` in a **try-catch block**, in case of any unforeseen errors.\
RPs can choose to default to an alternate authorisation mechanism in such error cases.
{% endhint %}

## **Following up after redirection**

Once the user has successfully scanned and authorised the transaction using SPM, Singpass JS will redirect the end-user’s browser/user-agent to the RP’s registered `redirect_uri` along with the `code` and `state` parameters.

Once redirected, the RP’s backend **should invoke the /txn-signatures** endpoint to retrieve the user’s transaction hash signature and complete the transaction signing flow.

Sample redirect location upon successful transaction signing

{% code overflow="wrap" %}

```
https://partner.txnsign.gov.sg/redirect?code=v2-BYE9fSQpDXCM0tlcI0yHwkwiff3bNy09n%2F703h4rmZI%3D&state=ewoidXNlciI6ImEiLAoic2Vzc2lvbiI6IjEyMyIsCiJyYW5kb20iOiJ4eXoiCn0=
```

{% endcode %}

## **Method Parameters**

**`DOMElementID`** `string`

Represents [the unique ID of the Document Object Model (DOM) Element](https://developer.mozilla.org/en-US/docs/Web/API/Element/id) where Singpass JS will render the transaction signing UI elements (QR code, text, images) on.

{% hint style="info" %}
The DOM element should be **empty**; Singpass JS will clear it before rendering any UI assets.
{% endhint %}

{% hint style="info" %}
See [UI dimensions](https://docs.sign.singpass.gov.sg/for-relying-parties/api-documentation/embedding-singpass-js#ui_dimensions) for the minimum size required of this element.
{% endhint %}

**`clientParams`** `object: { clientId, redirectUri }`

* **`clientId`** `string`

  The client identifier that was assigned by Singpass during RP registration.
* **`redirectUri`** `string`

  The URL that Singpass JS will eventually redirect the user to after the user completes the signing process using SPM. This value will be validated against the RP’s `redirect_uri` that was specified during RP registration.

***

**`transactionParamsSupplier`** `function: () ⇒ object: {state, nonce, txnInfo}`

This function will be invoked by Singpass to retrieve the necessary session parameters from the RP’s backend everytime a new sign session is to be initialised.

{% hint style="danger" %}
Singpass JS may invoke this function **multiple times** (eg. when the user clicks on the Singpass-rendered retry button to re-initialise another session due to an unforeseen error or session expiry). RPs should ensure that a **new** set of values (`txnInfo`, `state`, `nonce`) is returned on each invocation as these values are expected to be session-based.
{% endhint %}

{% hint style="danger" %}
RPs should ensure that they perform **automated retries on any network calls** made during this function invocation because Singpass **JS will not retry** on any failures arising from this function invocation.
{% endhint %}

This function can either be an `async` function **OR** a standard function that returns a `Promise` object that resolves to an object containing the following structure:

* **`state`** `string`

  A session-based, unique, and non-guessable value that RP’s backend should generate per transaction signing session.<br>

  As part of threat modelling, Singpass JS requests for the state parameter to mitigate replay attacks against the RP’s redirect uri. This parameter serves the same purpose as the [OAuth 2.0’s `state` parameter](https://tools.ietf.org/html/rfc6749#section-4.1.1).<br>

  It should have a **maximum of 255 characters** and **must match `regexp` pattern of `[A-Za-z0-9/_\-=.]`**.
* **`nonce`** `string`

  A session-based, unique, and non-guessable value that the RP’s backend should generate per transaction signing session.

  As part of threat modelling, Singpass JS requests for the nonce parameter to mitigate MITM attacks against the `/txn-signatures` endpoint. This parameter serves the same purpose as the [OIDC 1.0’s `nonce` parameter](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest).

  It should have a **maximum of 255 characters**. We recommend that you use a hex-encoded random number such as `java.security.SecureRandom` or `UUIDv4`.
* **`txnInfo`** `string`

  A signed JWT that contains the transaction information that the user would sign on using SPM. The structure of the JWT and how it will be validated by Singpass is detailed in the Structure of [`txnInfo` JWT section.](#structure-of-txninfo-jwt)

**Sample `transactionParamsSupplier` Implementation**

{% code overflow="wrap" %}

```javascript
const initiateSessionOnBackend = async () => {
  // Replace the below with an `await`ed call to initiate a session on your backend
  // which will generate state+nonce+txnInfo values.
  // Keep in mind to put in place automated retries, because Singpass will not retry on
  // any errors coming from this function invocation.

  //e.g
  return { state: "dummySessionState", nonce: "dummySessionNonce", txnInfo: "dummyTxnInfo" };
}

const transactionParamsSupplier = async () => {
  const backendSession = await initiateSessionOnBackend();
  return {
    state: backendSession.state,
    nonce: backendSession.nonce,
    txnInfo: backendSession.txnInfo
  };
};
```

{% endcode %}

***

**`onError`** `function: (errorId, message) ⇒ {}`

A callback function that will be invoked by Singpass JS upon certain error events.

On error events, you can expect the appropriate error screens to be rendered on the provided `DOMElementID` parameter.

The function must accept the following parameters:

* **`errorId`** `string`

  An Singpass-generated unique identifier for the error/request. If present, it can be used for debugging the error in Singpass.
* **`message`** `string`

  A short description of the error that occurred. Possible values:

  | Error Code                 | Description                                                                  |
  | -------------------------- | ---------------------------------------------------------------------------- |
  | `invalid_request`          | General client side errors, eg. an API call returned a 400 bad request error |
  | `status_subscription_fail` | The status subscription API call that Singpass JS makes has failed           |
  | `sign_session_expired`     | The current signing session has expired                                      |
  | `service_unavailable`      | Transaction signing service is toggled off at Singpass                       |
  | `txn_signing_fail`         | Catch all for unhandled/unknown errors                                       |
  | `txn_signing_cancelled`    | The current signing session is cancelled                                     |

**Sample `onError` implementation**

{% code overflow="wrap" %}

```javascript
const onError = (errorId, message) => {
  // Can be any custom implementation to handle the error

  // eg.
  console.log(`onError. errorId:${errorId} message:${message}`);
};
```

{% endcode %}

***

**`additionalOptions`** `object`

Optional configuration to customise additional behaviour:

* **`renderDownloadLink`** `string` (Optional)

  Whether to render a link to download the Singpass app. Defaults to false if not specified. The link appears below the QR area. Clicking the link opens the Singpass app webpage in a new tab/window.

|   | These additional UI elements will change the height of the rendered UI. See UI dimensions for the expected heights of the UI for the different modes. |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------- |

* **`appLaunchUrl`** `boolean` (Optional)

  Intended for mobile apps which embed this javascript and render a QR code. This adds the possibility for the user to be redirected back to the provided App Link after they successfully authorize themselves on the Singpass Mobile App. The value passed here should be the App Link registered with Apple’s App Store and/or Google’s Play Store.

  * Please note that opting into this would mean the relying party is required to be able to initialize auth, i.e generate the QR code, both with and without the appLaunchUrl. This is to ensure the Singpass App only triggers App Links when desired.

| Authenticating From                       | appLaunchUrl Param |
| ----------------------------------------- | ------------------ |
| Relying Party website                     | Do not include     |
| Relying Party website on a mobile browser | Do not include     |
| Relying Party mobile app                  | Can Include        |

* Sample `additionalOptions` implementation:

```
{
  renderDownloadLink: boolean,
  appLaunchUrl: string
}
```

## **Returns**

`initTxnSigning` returns a `string` that may be one of the following values:

* `SUCCESSFUL`

  Pre-flight checks were successful, and you can expect a QR code to be rendered. In case of unforeseen errors (eg. API errors during initialisation), a generic error screen will be rendered with the corresponding erroneous HTTP code, and the `onError` callback will be invoked.
* `FAILED`

  Pre-flight checks failed due to missing or malformed input parameters. If a valid `DOMElementID` is provided, a generic error screen will be rendered suggesting the user to opt for alternative authorization mechanisms. The `onError` callback will **NOT** be invoked.
* `NO_OP`

  Input method parameters are valid but pre-flight checks have determined that Singpass JS will not run on the end-user’s browser/user-agent. If a valid `DOMElementID` is provided, an error screen will be rendered indicating the unsupported browser/user-agent. The onError callback will **NOT** be invoked.

**Sample HTML invoking `.initTxnSigning()`**

{% code overflow="wrap" %}

```html
<html>
<script src="https://stg-id.singpass.gov.sg/static/ndi_txn_sign.js"></script>
<script>
  async function init() {
    const transactionParamsSupplier = async () => {
      const backendSession = await initiateSessionOnBackend();
      return {
        state: backendSession.state,
        nonce: backendSession.nonce,
        txnInfo: backendSession.txnInfo
      };
    };

    const onError = (errorId, message) => {
      console.log(`onError. errorId:${errorId} message:${message}`);
    };

    const initTxnSigningResponse = window.NDI.initTxnSigning(
      'singpass-qr',
      {
        clientId: 'T5sM5a53Yaw3URyDEv2y9129CbElCN2F',
        redirectUri: 'https://partner.gov.sg/redirect'
      },
      transactionParamsSupplier,
      onError,
      {
        renderDownloadLink: true,
        appLaunchUrl:'https://partner.gov.sg' // Replace with your iOS/Android App Link
      }
    );

    console.log('initTxnSigning: ', initTxnSigningResponse);
}
</script>
<body onload="init()">
<div id="singpass-qr"></div>
</body>
</html>
```

{% endcode %}

## **Structure of `txnInfo` JWT**

The `txnInfo` returned by the RP-provided `transactionParamsSupplier` function described above should wrap the required transaction information.

This is a standard signed JWT in [compact serialization format](https://tools.ietf.org/html/rfc7515#section-3.1).

Sample Signed JWT

{% code overflow="wrap" %}

```
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1vY2stY2xpZW50LWVzMjU2LTEifQ.eyJleHAiOjE2MTQ2NTk1MDAsImlzcyI6IlQ1c001YTUzWWF3M1VSeURFdjJ5OTEyOUNiRWxDTjJGIiwiYXVkIjoiaHR0cHM6Ly9zdGctaWQuc2luZ3Bhc3MuZ292LnNnL3R4bi1zaWduaW5nLXNlc3Npb25zIiwidHhuX2lkIjoidHJhbnNhY3Rpb25faWRlbnRpZmllciIsInR4bl9pbnN0cnVjdGlvbnMiOiJ1cGRhdGUgYmFuayBhY2NvdW50IGZyb20geHh4IHRvIHl5eSIsInR4bl9oYXNoIjoiYTRlNTlhNDBmODM2MjA0NDVlODk1ODcxZTU5Njg5NTcwZWQ2YjY0OTdkMzE1OTYxYTlkNDNmNDRkNDE4NzU4YiIsImlhdCI6MTYxNDY1OTM5Nywic3ViIjoiMWMwY2VlMzgtM2E4Zi00ZjhhLTgzYmMtN2EwZTRjNTlkNmE5In0.JTR09Ou6xBqo1nH19rTzrXyz4aW01_1JeMd6dcEFBJOAFAaVol3erfHvH6hDzA6m5RgfBfO-El6zQODY7o9uig
```

{% endcode %}

| Parameters                                                                                                                                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Header** - standard JWS Headers. Refer to <https://tools.ietf.org/html/rfc7515#section-4>.                                                          | <p><code>type</code> - Must be "JWT"<br><br><code>alg</code> - Must be an ECDSA-based algorithm. See complete list <a href="https://tools.ietf.org/html/rfc7518#section-3.4">here</a>.<br><br><code>kid</code> - The key id indicating the key used to sign the JWT</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| **Payload** - standard registered claims alongside custom claims highlighted in **bold**. Refer to <https://tools.ietf.org/html/rfc7519#section-4.1>. | <p><code>iat</code> - A Unix timestamp in seconds indicating the date and time when the JWT was issued. Must be at most 2 minutes from the current time.<br><br><code>exp</code> - A Unix timestamp in seconds indicating the date and time when the JWT will expire. Must not be greater than two minutes from <code>iat</code>.<br><br><code>sub</code> - (optional) The user’s Singpass ID (in UUIDv4 format) if available.<br><br><strong><code>txn\_id</code></strong> - The unique ID of the transaction generated by the RP.<br><br><strong><code>txn\_instructions</code></strong> - Human-readable instructions that the user can read and consent on. Any sensitive information must be masked.<br><br><strong><code>txn\_hash</code></strong> - Hashed value of the combined <code>txn\_id</code> and <code>txn\_instructions</code> claims in the format: <code>\<txn\_id>:\<txn\_instructions></code>. Must be <strong>SHA256 hashed and hex-encoded</strong>.</p> |
| **Signature**                                                                                                                                         | Standard [JWT signature](https://tools.ietf.org/html/rfc7515#section-3.3)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

**JWT Validation**

Singpass backend will validate the signature of the signed JWT using the RP’s JWKS endpoint that was provided during registration.

{% hint style="info" %}
Singpass caches the keys found in the RP’s JWKS endpoint for **one hour**.\
\
When the RP’s signing keypair needs to be rotated, new keypairs must use a **different key id (`kid`)** value to ensure that consumers (e.g. Singpass) of the keys do not use a stale version of the public key in their caches.
{% endhint %}

## `.cancelTxnSigningSession()` <a href="#cancel_txn_signing_session" id="cancel_txn_signing_session"></a>

This function does **not** require any method parameters, and it also does **not** return anything.

Upon invocation, all ongoing API calls (if any) made by `.initTxnSession()` will be aborted. A [screen](https://stg-id.singpass.gov.sg/docs/txn-signing/js#ui_cancelled) indicating that the transaction has been cancelled is rendered, and there will be no UI changes thereafter.

If a valid [`onError`](https://stg-id.singpass.gov.sg/docs/txn-signing/js#on_error_callback) callback is provided, then it will be invoked with the following parameters:\
`onError(errorId: '', message: 'txn_signing_cancelled')`

{% hint style="info" %}
We recommend invoking this function when a **transaction has been cancelled by the user** (eg. when the transaction modal is closed) to ensure that the transaction is aborted properly.\
It is safe to invoke this method regardless of the session’s state (ie. whether the session is active or in an erroneous state).
{% endhint %}
