Generate a JWT

During onboarding to production, you must generate at least one pair of public and private keys for OpenID authentication. Then, you must generate and sign a JSON Web Token (JWT) for each key pair that you generate.

Generating access keys

Before you can log in to the Pismo platform, you need a server key (server_key) and a secret key (secret_key). The simplest way to generate access keys is to use Pismo Control Center. For details, see Manage access keys.


Secret key is displayed only once

After you generate your secret key, you should save it somewhere. Otherwise, you'll have to generate a new one the next time you need to provide it.

Generate your JWT

To generate JWTs, Pismo recommends using one of the libraries provided at A


Requests involving account information

  • If you need to access information about an account, you must request an account-specific token.
  • If you need to access information about an account that uses an external account ID, you must request an account-id token.
  1. Use the OpenSSL toolkit to generate a private key and a public key.
Generate a private key:
  $ openssl genpkey -out rsakey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
Generate a public key based on the private key yougenerated:
  $ openssl rsa -in rsakey.pem -out -pubout -outform PEM


Another option is to use Google/Firebase helper libraries, where you use the private key in Google's Service Account format and provide it to the libraries. For more information about this implementation, refer to the Google Cloud Platform (GCP) documentation.

  1. Store your private key securely, and send the public key to your Pismo representative, along with your Org ID (also known as Tenant ID).
  2. Generate your JWT using the private key associated with the pubic key you shared
  3. Obtain an access token from Pismo
  4. Use this access token to make API calls.

The following example shows how to create a signed JWT in a few languages:

package main

import (


var mySigningKey = []byte(`

func main() {

    key, err := jwt.ParseRSAPrivateKeyFromPEM(mySigningKey)

    token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
        "iss":       "[email protected]",
        "aud":       "",
        "exp":       time.Now().Add(time.Hour).Unix(),
        "iat":       time.Now().Unix(),
        "sub":       "[email protected]",
        "uid":       "12345678910",
        "tenant_id": "test-pbrj4",

    // Sign and get the complete encoded token as a string using the secret
    tokenString, err := token.SignedString(key)
    fmt.Println(tokenString, err)


import java.nio.charset.Charset;
import java.nio.file.Files;
import java.time.Instant;
import java.util.*;

import io.jsonwebtoken.*;

public class JwtTokenwithRsa {

	//Reference to your private key
	public static final String PRIVATE_KEY_FILE_PATH = "RSA/rsakey.pem";

	public static void main(String[] args) {
		try {
			JwtTokenwithRsa tokenwithRsa = new JwtTokenwithRsa();

		} catch (Exception e) {

	public String generateJwtToken(PrivateKey privateKey) {
		JwtBuilder builder = Jwts.builder().signWith(SignatureAlgorithm.RS256, privateKey);

		long issuedAt =;
		long expiresAt =;
		Map<String,String> pismoClaims = new HashMap<String,String>();

		builder.claim("aud", "value provided by Pismo");
		builder.claim("sub", "value provided by Pismo");
		builder.claim("iss", "value provided by Pismo");
		builder.claim("exp", expiresAt);
		builder.claim("pismo", pismoClaims);
		builder.claim("tenant_id", "Your Tenant ID");
		builder.claim("uid", "Account Id or Tenant Id");

		String token = builder.compact();
		return token;

	public void generateJWTWithRsa() throws Exception {
		KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");

		File f = new File(PRIVATE_KEY_FILE_PATH);
		PrivateKey privateKey = readPrivateKey(f);

		// Generate Token
		String id = UUID.randomUUID().toString();
		String token = generateJwtToken(privateKey);


	public RSAPrivateKey readPrivateKey(File file) throws Exception {
		String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

		String privateKeyPEM = key
				.replace("-----BEGIN PRIVATE KEY-----", "")
				.replaceAll(System.lineSeparator(), "")
				.replace("-----END PRIVATE KEY-----", "");

		byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);

		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
		return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);

To be released
To be released

The following tables lists the claims that the JWT should contain.

algFixed string valueYesRS256JWT signing algorithm (RSA using SHA256). Value provided by Pismo.
issFixed string valueYesJWT issuer. Value provided by Pismo.
subFixed string valueYesJWT subject. Value provided by Pismo.
audFixed string valueYesJWT audience. Value provided by Pismo
iatTimestamp numberYesex: 1697044489JWT issued at. Time at which the token was issued (Unix epoch format).
expTimestamp numberYesex: 1697048089Time at which the token expires (Unix epoch format). This can be a maximum of 3600 seconds (one hour) after iat.
tenant_idFixed string valueYesOrganization ID. Value provided by Pismo.
uidString valueYesPismo account ID. If you don't want the token to contain an account ID, you can use the same value here that you used fortenant_id. For more information, see Account-specific access tokens.
pismoObject typeYes"pismo": {
"group": "pismo-v2:bankaccounts:rw"
Group ID that the token grants access to. A group has access to a certain subset of endpoints. Ask your Pismo representative for more information on the groups that can be used.
customClaims Object typeNo"customClaims": {
"custom1": "customValue",
"userexample": "myusername"
Optional custom claims.

Using the JWT to request an access token

The following sample code shows a cURL POST request for an access token in the Pismo Sandbox:

curl --request POST \
--url \
--header 'Content-Type: application/json' \
--data '{

Handling the response

If the JWT is properly formed and your service has permission to perform the operation, then the JSON response from the authorization server should include an access token. The following is an example response.

  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InNrSUJOZyJ9.eyJpc3MiOiJodHRwczovL2lkZW50aXR5dG9vbGtpdC5nb29nbGUuY29tLyIsImF1ZCI6ImRldi1na2UtN2E2NSIsImlhdCI6MTU3NjE4MDg4MSwiZXhwIjoxNTc3MzkwNDgxLCJ1c2VyX2lkIjoiY2FwaXRhb2FtZXJpY2FAcGlzbW8uaW8iLCJzaWduX2luX3Byb3ZpZGVyIjoiY3VzdG9tIiwiZXh0cmFfY2xhaW1zIjp7InRlc3QxIjoidGVzdDEiLCJ0ZXN0MiI6InRlc3QyIiwidGVzdDMiOiJ0ZXN0MyJ9fQ.LAv--CRbsZqDhlRXcpnZlyK403oU2i1__fFR7CZ-bgp5e4lF187r1W-9jpZCVLit-QhneHJiaySxlDTAiwYfUu6bpVPw0BaXT-GimmGi-MDudUX3R44VtdQPvOXUHgx43DDD1NsDOwDn5QtrA-pvrfyT-Ijs6GcH4R66XPYaIm_ASHSEPCupK0ZW6VFjiWx3goGtCKG-TQlaGyq-iQ_ZBRJksJf6xIN8bFC1HqODePm3yDbtum2hbxwuZGtBQ0DcgkH4in4TWKwe8qrk37cl5TOkRx1yyhh9Jkhigmu6NuTvCk6ic-aappIR2P9iBB-g3W33-HVutXqqWfd0DXQb5w",
  "refresh_token": "AE0u-NcsvZAKSuwq8Q4E7ZEE9NBmmio7-0JQASKqw8L2AWaFH9EIggs4wuzvE0nnVoC9",
  "expires_in": "3600"


Access tokens expire after one hour

Your application should catch the error generated when this happens. It should then repeat the JWT request process.

Calling Pismo APIs

After your application obtains an access token, you can use it to make calls to a Pismo API on behalf of the given user account. To do this, include the access token in the authorization: Bearer header of each request, as example below.

curl --request POST \
  --url \
  --header 'Content-Type: application/json' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InNrSUJOZyJ9.eyJpc3MiOiJodHRwczovL2lkZW50aXR5dG9vbGtpdC5nb29nbGUuY29tLyIsImF1ZCI6ImRldi1na2UtN2E2NSIsImlhdCI6MTU3NjE4MDg4MSwiZXhwIjoxNTc3MzkwNDgxLCJ1c2VyX2lkIjoiY2FwaXRhb2FtZXJpY2FAcGlzbW8uaW8iLCJzaWduX2luX3Byb3ZpZGVyIjoiY3VzdG9tIiwiZXh0cmFfY2xhaW1zIjp7InRlc3QxIjoidGVzdDEiLCJ0ZXN0MiI6InRlc3QyIiwidGVzdDMiOiJ0ZXN0MyJ9fQ.LAv--CRbsZqDhlRXcpnZlyK403oU2i1__fFR7CZ-bgp5e4lF187r1W-9jpZCVLit-QhneHJiaySxlDTAiwYfUu6bpVPw0BaXT-GimmGi-MDudUX3R44VtdQPvOXUHgx43DDD1NsDOwDn5QtrA-pvrfyT-Ijs6GcH4R66XPYaIm_ASHSEPCupK0ZW6VFjiWx3goGtCKG-TQlaGyq-iQ_ZBRJksJf6xIN8bFC1HqODePm3yDbtum2hbxwuZGtBQ0DcgkH4in4TWKwe8qrk37cl5TOkRx1yyhh9Jkhigmu6NuTvCk6ic-aappIR2P9iBB-g3W33-HVutXqqWfd0DXQb5w' \
  --data '{< JSON body the service expects to receive>}'