Learn how to sign and encypt with a single call.
This guide explains how to use JOSE JWS + JWE to implement the highest level of API security for your integration with Wise. This combined approach provides complete protection: signatures verify the sender's identity and prevent tampering, while encryption ensures that only intended recipients can read the message content.
The signing and encryption is completed via a combined JWS and JWE mechanism, supported by JOSE. This approach provides both authenticity (through signing) and confidentiality (through encryption).
Generally, this means:
- The client creates two key pairs: one for signing and one for encryption, and delivers both public keys to Wise (please contact your implementation manager for details). The format, type, and details required are included below.
- Wise installs both public keys to be consumed by our API.
- Once installed, the partner can now call JWS+JWE enabled endpoints with signed and encrypted request bodies.
- Wise will first decrypt the request using Wise's private encryption key, then verify the signature using the partner's public signing key, and perform other checks.
- Wise will return the response signed and encrypted - first signing the response, then encrypting the signed JWT. The partner's system must accept the media type provided (see details below).
- The partner decrypts the response using their private encryption key, then verifies and decodes the signature using Wise's public signing key.
Contact your implementation manager about submitting your public key details to Wise.
When delivering the signing public key, we require the following information:
- Key Material
- Algorithm used (ES256, ES384, ES512, PS256, PS384, or PS512)
- Scope:
PAYLOAD_SIGNING - KeyID: UUID (Optional)
- Expiration Timestamp (Optional)
- Deletion Timestamp (Optional)
- Activation Timestamp (Optional)
When delivering the encryption public key, we require the following information:
- Key Material
- Algorithm used (RSA-OAEP-256)
- Scope:
PAYLOAD_ENCRYPTION - KeyID: UUID (Optional)
- Expiration Timestamp (Optional)
- Deletion Timestamp (Optional)
- Activation Timestamp (Optional)
Expiration, deletion, and activation timestamps are all optional. If not included, once installed, the keys will remain active until Wise is instructed to remove them.
The KeyID, denoted in the JWS header as kid is an indicator of which key to use to validate the signature or decrypt the payload.
Here is a step-by-step workflow with example commands to manually sign and encrypt a request.
Create a request body as-per our usual API reference, and sign it with the request URL in the header block (see example).
{
"alg": "ES512",
"typ": "JWT",
"kid": "663a0e44-aa4a-4ff0-a9f8-cd99f5fbad71",
"url": "/v1/auth/jose/playground/jwsjwe"
} {
"message": "This is an example from docs.wise.com"
} The header and body should then be base64url encoded and signed, with the signature appended as per the JOSE standard for JWS. It is recommended that an established library be used to complete this process to avoid difficulties and errors in the encoding and signing process.
The example here uses the header and body as shown above and signs it with
your private signing key. The three parts of this JWS are:
- Base64(base64url) Encoded Header
- Base64(base64url) Encoded Body
- Signed header + Body
eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6IjY2M2EwZTQ0LWFhNGEtNGZmMC1hOWY4LW
NkOTlmNWZiYWQ3MSIsInVybCI6Ii92MS9hdXRoL2pvc2UvcGxheWdyb3VuZC9qd3Nqd2UifQ.eyJtZ
XNzYWdlIjoiVGhpcyBpcyBhbiBleGFtcGxlIGZyb20gZG9jcy53aXNlLmNvbSJ9.AS9QHdkWvUEn0L
xQEMPRBzlceN78J-Le-Qm1XIkkSBpsGdc0WM0MZTIGFEAJEcWeUR2M-abtd5DRdar4hLzs9apPAQ-G
T70SIDV6pX9-4UKfIfzJ4g305zCoHflTfn-ijvI7XrVR_yr7xO9GJo86bfBqAX_m5uuxyU7Jy9gM1e
pd8HcCThe signed JWT from step 2 must now be encrypted using Wise's public encryption key. First, retrieve Wise's public encryption key using the endpoint. See Get Wise's public key.
Then encrypt the entire signed JWT string using the RSA-OAEP-256 algorithm
with Wise's public key. The result will be a JWE token with five parts
separated by dots:
- JWE Protected Header (Base64url encoded)
- JWE Encrypted Key (Base64url encoded)
- JWE Initialization Vector (Base64url encoded)
- JWE Ciphertext (Base64url encoded)
- JWE Authentication Tag (Base64url encoded)
eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.W0fuxaZOoyaBcxWwgtjEPkOnLd
VNyH9ncZi5Y9xQbjD4sYJn8vEQmxKHDw5s14sWhdexSNAPTVMSyzwJaA-LRL0tZTEuQ.ohD4LLjImi
KOV4Tu.Rgp9mc2JD6m9Zm5htSqrejwWYy0_hIylYdLD39ZCR-VnQ2VX-Tot8kKGeNncnv7hJ_ApANW
iJpKJbiM.5hNt-uaxuOkOraGGZSmsig The request must include the following headers (all are required):
- Content-Type: application/jose+json
- Accept: application/jose+json
- X-TW-JOSE-Method: jws+jwe
- Accept-Encoding: identity
curl -X POST 'https://api.wise-sandbox.com/v1/auth/jose/playground/jwsjwe' \
-H 'Authorization: Bearer XXXXXXX' \
-H 'Content-Type: application/jose+json' \
-H 'Accept: application/jose+json' \
-H 'X-TW-JOSE-Method: jws+jwe' \
-H 'Accept-Encoding: identity' \
-d 'eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.W0fuxaZOoyaBcxWwg
tjEPkOnLdVNyH9ncZi5Y9xQbjD4sYJn8vEQmxKHDw5s14sWhdexSNAPTVMSyzwJaA-LRL0tZTEuQ.o
hD4LLjImiKOV4Tu.Rgp9mc2JD6m9Zm5htSqrejwWYy0_hIylYdLD39ZCR-VnQ2VX-Tot8kKGeNncnv
7hJ_ApANWiJpKJbiM.5hNt-uaxuOkOraGGZSmsig' The response from Wise will be encrypted and signed. To process it:
First, decrypt the response using your private encryption key. The response is a JWE token that must be decrypted using the RSA-OAEP-256 algorithm with your private encryption key.
eyJraWQiOiJiMzc0ODE1MC0xNGQ0LTRlMDAtYjM5NC03YWUwYzkzYmUyNDUiLCJlbmMiOiJBMjU2R0
NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.VGhpcyBpcyB0aGUgZW5jcnlwdGVkIENFSw.aXZfdmFsd
WU.ZW5jcnlwdGVkX3BheWxvYWQ.YXV0aF90YWcAfter decryption, you will have a signed JWT (JWS) from Wise.
Next, verify the signature of the decrypted JWT using Wise's public signing key. Fetch Wise's latest public signing key, see Get Wise's public key:
curl -X GET 'https://api.wise-sandbox.com/v1/auth/jose/response/public-keys?al
gorithm=ES512&scope=PAYLOAD_SIGNING' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <your api token>'Use Wise's public signing key to verify the signature and decode the payload.
{
"message": "jose-playground-response-This is an example from docs.wise.com",
"method": "POST"
} Wise will match the signing algorithm specified in your request header. If no algorithm is specified, Wise will use the algorithm associated with your currently active public key.
Request Flow
- Sign: Create and sign your payload using your private signing key (JWS)
- Encrypt: Encrypt the signed JWT using Wise's public encryption key (JWE)
- Send: Submit the encrypted token to Wise with required headers
Response Flow
- Decrypt: Decrypt Wise's response using your private encryption key (JWE)
- Verify: Verify the signature using Wise's public signing key (JWS)
- Process: Extract and use the verified payload
This approach ensures:
- Authenticity: The signature proves the message came from the claimed sender
- Integrity: The signature ensures the message hasn't been tampered with
- Confidentiality: The encryption ensures only the intended recipient can read the message
- Non-repudiation: The signature provides proof of origin that cannot be
denied
Use the Playground JWS+JWE endpoint to test your JWS+JWE implementation in a safe environment before production use.