Skip to main content

Widgets

Creating a widget

As a business customer, you may have one or more widgets which will allow your end users to interact with Topper. A widget is associated with one of the flows supported by Topper — crypto on-ramp or crypto off-ramp.

Each widget has its own:

  • Signing keys: Keys used to sign and verify bootstrap tokens.
  • Events and webhooks: Used to receive updates on end user sessions.

During the onboarding process, you will be guided through the process of creating the widgets required for your business needs, and you will be provided with a widget id for each.

Generating a signing key

You must generate at least one asymmetric key per widget.

The public key should be sent to Topper as part of the onboarding process, and you will be provided with a unique key id which associates your public key with your widget. The private key will be used to sign bootstrap tokens, which you will use to initiate Topper sessions for your end users.

Topper supports the following key algorithms:

  • Elliptic Curve (ES256, ES384, ES512)
  • RSA (RS256, RS384, RS512)

Here is a quick Node.js snippet to generate an ES256 key pair in Topper's preferred JWK format:

import { generateKeyPairSync } from 'crypto';

// Generate a key pair.
const alg = 'ES256';
const { privateKey, publicKey } = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });

// Output in JWK format.
console.log('Private JWK:');
console.log(JSON.stringify({ alg, ...privateKey.export({ format: 'jwk' }) }));
console.log('Public JWK:');
console.log(JSON.stringify({ alg, ...publicKey.export({ format: 'jwk' }) }));

// Private JWK:
// {"alg":"ES256","kty":"EC","x":"dxk0WKKhOyFbU0eZD0plgOB8l9rM-SD5NDgnGpvg99o","y":"nIMebHLyyisqfQKkb-bCp6dVNwVqDR3FLA5ZWUZ_yQ8","crv":"P-256","d":"mdAQEjZBkxtoVeque2wXqebfo1HY0_C2uGApqeKEaX8"}
// Public JWK:
// {"alg":"ES256","kty":"EC","x":"dxk0WKKhOyFbU0eZD0plgOB8l9rM-SD5NDgnGpvg99o","y":"nIMebHLyyisqfQKkb-bCp6dVNwVqDR3FLA5ZWUZ_yQ8","crv":"P-256"}

Initiating a session

To initiate a Topper session on behalf of your end user you must generate and sign a bootstrap token, a JSON Web Token with a payload configuring the Topper widget, signed by the private key generated when creating the widget during onboarding.

The header of the bootstrap token must contain the following data:

  • typ: Must be "JWT".
  • alg: The algorithm used to sign the payload, for example "ES256".
  • kid: The key id associated with the public key generated when creating the widget.

The payload of the bootstrap token must contain the following claims:

  • iat The timestamp the token was issued (number of seconds since the Unix epoch).
  • jti: A unique ID for the session which will be used for updates in events or webhooks.
  • sub: The widget id of the widget.

The payload should also contain configuration specific to the widget for which the session is being created.

Here is a Node.js snippet to generate a bootstrap token for the crypto on-ramp flow using the jsonwebtoken package. Please note that the code below is for illustrative purposes only, and the integration should be adapted to your application.

import { createPrivateKey, randomUUID } from 'node:crypto';
import { promisify } from 'node:util';
import jsonwebtoken from 'jsonwebtoken';

// Function to create a bootstrap token signer to be reused.
const createBootstrapTokenSigner = (widgetId, keyId, jwk) => {
const jwkObject = JSON.parse(jwk);
const privateKey = createPrivateKey({ format: 'jwk', key: jwkObject });
const options = { algorithm: jwkObject.alg, keyid: keyId };

// Promisify `jsonwebtoken.sign()` function to use async/await.
const signJwt = promisify(jsonwebtoken.sign);

return async claims => {
claims = { ...claims, sub: widgetId };

return await signJwt(claims, privateKey, options);
};
};

// Widget id example supplied by Topper.
const widgetId = 'd259f6ac-3e8d-46eb-9e6c-c92e138b7660';
// Key id example supplied by Topper.
const keyId = 'c084a85d-c486-4035-9c60-8cec81d8b8f5';
// Private JWK example you generated.
const jwk = '{"alg":"ES256","kty":"EC","x":"dxk0WKKhOyFbU0eZD0plgOB8l9rM-SD5NDgnGpvg99o","y":"nIMebHLyyisqfQKkb-bCp6dVNwVqDR3FLA5ZWUZ_yQ8","crv":"P-256","d":"mdAQEjZBkxtoVeque2wXqebfo1HY0_C2uGApqeKEaX8"}';

// Example of creating a bootstrap token.
const signBootstrapToken = createBootstrapTokenSigner(widgetId, keyId, jwk);
const bootstrapToken = await signBootstrapToken({
jti: randomUUID(),
source: {
amount: '100.00',
asset: 'USD'
},
target: {
address: '0xb794f5ea0ba39494ce839613fffba74279579268',
asset: 'ETH',
network: 'ethereum',
label: 'My wallet'
}
});

console.log('Bootstrap token:', bootstrapToken);

After a bootstrap token has been generated, a Topper session can be started by opening a browser window for the end user using Topper's app URL. The bootstrap token should be added to the bt parameter of the query string.

https://app.sandbox.topperpay.com/?bt=<bootstrap token>

Using the Web SDK, the configuration process is seamless, allowing you to choose between multiple ways of initializing Topper and have support for browser events:

const topper = new TopperWebSdk({ environment: TOPPER_ENVIRONMENTS.SANDBOX });

topper.initialize({ bootstrapToken: <bootstrap token> });

In order to prevent social and replay attacks, a bootstrap token will only be valid for 3 minutes after its issue time (from the iat claim). A bootstrap token may only be used to create a session one time, any subsequent attempts to create a session with the same token will be rejected.

Initiating multiple sessions

To allow both crypto onramp and crypto offramp flows to be available when Topper is initialized, you can initialize multiple sessions by providing multiple bootstrap tokens.

https://app.sandbox.topperpay.com/?bt=<crypto_onramp_bootstrap_token>;<crypto_offramp_bootstrap_token>

Using the Web SDK:

const topper = new TopperWebSdk({ environment: TOPPER_ENVIRONMENTS.SANDBOX });

topper.initialize({ bootstrapTokens: [<crypto_onramp bootstrap token>, <crypto_offramp bootstrap token>] });

By default, the widget will use the first bootstrap token passed in the URL as the active flow. However, an optional active_flow parameter in the URL or activeFlow configuration option in the Web SDK can be used to explicitly set which flow is active on initialization. The available options are crypto_onramp and crypto_offramp.

A note about security

For security reasons, the widget id and key id we provide for the sandbox and production environments will be different. Moreover, you should not use the same signing key for both environments.

As the name implies, signing private keys should be kept secure. Do not expose them to clients. If a signing private key is compromised in any of the environments, reach out to us and we will delete the associated public key from your widget.