For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Dashboard
GuidesAPI ReferenceSDKs
GuidesAPI ReferenceSDKs
  • Get Started
    • Overview and Solutions
    • Choose an Integration
    • Quickstart
    • Branding Guidelines
    • Get Support
  • Accept Payments
    • Instant Payments
    • Trustly Pay
    • Recurring Payments
    • Scan and Pay
    • Remember Me
    • Payment Integration Checklist
  • Send Money
    • Send Payouts Using Online Banking
    • Send Payouts Using Account Information
    • International Transfers
  • Retrieve Data
    • Verify Accounts Using Online Banking
    • Verify Accounts Using Micro-Deposits
    • Retrieve Bank and User Information
    • Tokenize Bank Information
    • Trustly ID
    • Insights Data
  • Core Concepts
    • Key Concepts
    • The Establish Data Object
    • Transactions and Transaction IDs
    • Tokens and Account Security
    • Redirect URLs and Return Flow
    • Webhooks and Events
    • Content Strings
  • API Fundamentals
    • Authentication and OAuth
    • Secure Requests and Signature Validation
      • Generate request signatures
      • Validate the redirect signature
      • Validate the notification signature
      • Encrypt a field value
    • Idempotency
    • Testing
    • Status codes and type definitions
  • Manage Your Integration
    • Go-Live Checklist
    • Merchant Portal
    • Reports and Reconciliation
    • Refresh Bank Authorization
    • Override Risk Declines
    • VIP Tiers
    • Financial Institution Status
Dashboard
Products
PaymentsDataPayouts
Company
AboutCareersContact Sales

Terms of Use | Privacy Policy | © 2026 Trustly, Inc.

Developer-friendly docs for your API
GitHub|Contact Support|Business Help Center|Merchant Portal
Terms of Use|Privacy Policy|© 2026 Trustly, Inc.
Developer-friendly docs for your API
LogoLogo
North AmericaEurope
North AmericaEurope
On this page
  • Hashing algorithm options (BETA functionality)
  • Parameter ordering
  • Examples
API FundamentalsSecure Requests and Signature Validation

Generate request signatures

|View as Markdown|Open in Claude|
Was this page helpful?
Previous

Secure requests and validate signatures

Next

Validate the redirect signature

Built with

A request signature is essential for the Establish Data used by the Trustly UI and the POST /establish API endpoint to ensure the integrity of transactions, providing robust protection against potential tampering. The requestSignature should be generated server-side to keep your accessKey secure and out of client-side code. It is a Base64-encoded HMAC-SHA1 hash of the data in the request payload, created using your accessKey.

To generate a request signature, you complete the following tasks:

  • Concatenate Parameters - Gather all the request parameters, including the accessId and then concatenate these parameters into a single string. Ensure the parameters are in the correct order.
  • Hash the Concatenated String - Use the HMAC-SHA1 hashing algorithm, use your accessKey as the key for the HMAC algorithm, and then Base64 encode the resulting hash.
  • Attach the Signature to Your Request - Include the generated signature in the requestSignature property of the payload.

Hashing algorithm options (BETA functionality)

If desired, an alternative hashing algorithm may be used to generate the requestSignature. Trustly will assume by default that HMAC-SHA1 has been used to generate the hash so if an alternative is used, the algorithm name and a colon should be prepended to the resulting signature after it is Base64 encoded. For example:

requestSignature: "HmacSHA512:RuYv5esOLn2f4F4NU5bz7YGLITEtLVQrciiEm0dCrn/O1DJ9E5hLwIYTyd5DHBJBxAhdxuKp655bG/gymoPt+g=="

Supported algorithms include:

  • HMAC-SHA1: HmacSHA1 (Default value if no algorithm prefix is included)
  • HMAC-SHA512: HmacSHA512

Parameter ordering

In order for the signature to be validated correctly, the order of parameters in the hash must be strictly maintained. Any field in the list below which is present in the request payload must be included in the generated requestSignature and must appear in the order below.

Do not include any parameters that are not present in the request payload. Additionally, do not include fields with null values unless they also appear in your request payload with a null value.

  • accessId
  • merchantId
  • description
  • currency
  • amount
  • displayAmount
  • minimumBalance
  • merchantReference
  • paymentType
  • timeZone
  • recurrence.startDate
  • recurrence.endDate
  • recurrence.frequency
  • recurrence.frequencyUnit
  • recurrence.frequencyUnitType
  • recurrence.recurringAmount
  • recurrence.automaticCapture
  • verification.status
  • verification.verifyCustomer
  • customer.customerId
  • customer.externalId
  • customer.name
  • customer.vip
  • customer.taxId
  • customer.driverLicense.number
  • customer.driverLicense.state
  • customer.address.address1
  • customer.address.address2
  • customer.address.city
  • customer.address.state
  • customer.address.zip
  • customer.address.country
  • customer.phone
  • customer.email
  • customer.balance
  • customer.currency
  • customer.enrollDate
  • customer.externalTier
  • customer.externalTierTrustScore
  • customer.dateOfBirth
  • account.nameOnAccount
  • account.name
  • account.type
  • account.profile
  • account.accountNumber
  • account.routingNumber
  • beneficiary.name
  • beneficiary.taxId
  • beneficiary.address.address1
  • beneficiary.address.city
  • beneficiary.address.state
  • beneficiary.address.zip
  • beneficiary.address.country
  • beneficiary.dateOfBirth
  • beneficiaryAccount.iban
  • beneficiaryAccount.paymentProvider.name
  • beneficiaryAccount.paymentProvider.routingNumber
  • beneficiaryAccount.paymentProvider.swift
  • beneficiaryAccount.paymentProvider.country
  • transactionId
  • onlinePPSubtype
  • customer.customData.payins.volume30Days
  • customer.customData.payins.volume90Days
  • customer.customData.payins.volume365Days
  • customer.customData.payouts.volume30Days
  • customer.customData.payouts.volume90Days
  • customer.customData.payouts.volume365Days

Examples

For a functional example in the context of a backend application, see the NestJS example. The following examples are provided here:

  • Readable but verbose JavaScript example
  • Optimized ES6 JavaScript example
  • Java example
1const Crypto = require('crypto');
2const generateSignature = (establishData, accessKey) => {
3 let query = '';
4 query += `accessId=${establishData.accessId}`;
5 query += `&merchantId=${establishData.merchantId}`;
6 query += `&description=${establishData.description}`;
7 query += `&currency=${establishData.currency}`;
8 query += `&amount=${establishData.amount}`;
9
10 if (establishData.displayAmount) query += `&displayAmount=${establishData.displayAmount}`;
11 if (establishData.minimumBalance) query += `&minimumBalance=${establishData.minimumBalance}`;
12
13 query += `&merchantReference=${establishData.merchantReference}`;
14 query += `&paymentType=${establishData.paymentType}`;
15
16 if (establishData.timeZone) query += `&timeZone=${establishData.timeZone}`;
17
18 if (establishData.paymentType === 'Recurring' && establishData.recurrence) {
19 if (establishData.recurrence.startDate) query += `&recurrence.startDate=${establishData.recurrence.startDate}`;
20 if (establishData.recurrence.endDate) query += `&recurrence.endDate=${establishData.recurrence.endDate}`;
21 if (establishData.recurrence.frequency) query += `&recurrence.frequency=${establishData.recurrence.frequency}`;
22 if (establishData.recurrence.frequencyUnit) query += `&recurrence.frequencyUnit=${establishData.recurrence.frequencyUnit}`;
23 if (establishData.recurrence.frequencyUnitType) query += `&recurrence.frequencyUnitType=${establishData.recurrence.frequencyUnitType}`;
24 if (establishData.recurrence.recurringAmount) query += `&recurrence.recurringAmount=${establishData.recurrence.recurringAmount}`;
25 if (establishData.recurrence.automaticCapture) query += `&recurrence.automaticCapture=${establishData.recurrence.automaticCapture}`;
26 }
27
28 if (establishData.verification) {
29 if (establishData.verification.status) query += `&verification.status=${establishData.verification.status}`;
30 if (establishData.verification.verifyCustomer) query += `&verification.verifyCustomer=${establishData.verification.verifyCustomer}`;
31 }
32
33 if (establishData.customer) {
34 if (establishData.customer.customerId) query += `&customer.customerId=${establishData.customer.customerId}`;
35 if (establishData.customer.externalId) query += `&customer.externalId=${establishData.customer.externalId}`;
36 if (establishData.customer.name) query += `&customer.name=${establishData.customer.name}`;
37 if (establishData.customer.vip !== undefined) query += `&customer.vip=${establishData.customer.vip}`;
38 if (establishData.customer.taxId) query += `&customer.taxId=${establishData.customer.taxId}`;
39 if (establishData.customer.driverLicense) {
40 if (establishData.customer.driverLicense.number) query += `&customer.driverLicense.number=${establishData.customer.driverLicense.number}`;
41 if (establishData.customer.driverLicense.state) query += `&customer.driverLicense.state=${establishData.customer.driverLicense.state}`;
42 }
43 if (establishData.customer.address) {
44 if (establishData.customer.address.address1) query += `&customer.address.address1=${establishData.customer.address.address1}`;
45 if (establishData.customer.address.address2) query += `&customer.address.address2=${establishData.customer.address.address2}`;
46 if (establishData.customer.address.city) query += `&customer.address.city=${establishData.customer.address.city}`;
47 if (establishData.customer.address.state) query += `&customer.address.state=${establishData.customer.address.state}`;
48 if (establishData.customer.address.zip) query += `&customer.address.zip=${establishData.customer.address.zip}`;
49 if (establishData.customer.address.country) query += `&customer.address.country=${establishData.customer.address.country}`;
50 }
51 if (establishData.customer.phone) query += `&customer.phone=${establishData.customer.phone}`;
52 if (establishData.customer.email) query += `&customer.email=${establishData.customer.email}`;
53 if (establishData.customer.balance) query += `&customer.balance=${establishData.customer.balance}`;
54 if (establishData.customer.currency) query += `&customer.currency=${establishData.customer.currency}`;
55 if (establishData.customer.enrollDate) query += `&customer.enrollDate=${establishData.customer.enrollDate}`;
56 if (establishData.customer.dateOfBirth) query += `&customer.dateOfBirth=${establishData.customer.dateOfBirth}`;
57 }
58
59 if (establishData.account) {
60 if (establishData.account.nameOnAccount) query += `&account.nameOnAccount=${establishData.account.nameOnAccount}`;
61 if (establishData.account.name) query += `&account.name=${establishData.account.name}`;
62 if (establishData.account.type) query += `&account.type=${establishData.account.type}`;
63 if (establishData.account.profile) query += `&account.profile=${establishData.account.profile}`;
64 if (establishData.account.accountNumber) query += `&account.accountNumber=${establishData.account.accountNumber}`;
65 if (establishData.account.routingNumber) query += `&account.routingNumber=${establishData.account.routingNumber}`;
66 }
67
68 // Required for International Money Transfer
69 if (establishData.beneficiary) {
70 if (establishData.beneficiary.name) query += `&beneficiary.name=${establishData.beneficiary.name}`;
71 if (establishData.beneficiary.taxId) query += `&beneficiary.taxId=${establishData.beneficiary.taxId}`;
72 if (establishData.beneficiary.address) {
73 if (establishData.beneficiary.address.address1) query += `&beneficiary.address.address1=${establishData.beneficiary.address.address1}`;
74 if (establishData.beneficiary.address.city) query += `&beneficiary.address.city=${establishData.beneficiary.address.city}`;
75 if (establishData.beneficiary.address.state) query += `&beneficiary.address.state=${establishData.beneficiary.address.state}`;
76 if (establishData.beneficiary.address.zip) query += `&beneficiary.address.zip=${establishData.beneficiary.address.zip}`;
77 if (establishData.beneficiary.address.country) query += `&beneficiary.address.country=${establishData.beneficiary.address.country}`;
78 }
79 if (establishData.beneficiary.dateOfBirth) query += `&beneficiary.dateOfBirth=${establishData.beneficiary.dateOfBirth}`;
80 }
81 if (establishData.beneficiaryAccount) {
82 if (establishData.beneficiaryAccount.paymentProvider) {
83 if (establishData.beneficiaryAccount.paymentProvider.name) query += `&beneficiaryAccount.paymentProvider.name=${establishData.beneficiaryAccount.paymentProvider.name}`;
84 if (establishData.beneficiaryAccount.paymentProvider.routingNumber) query += `&beneficiaryAccount.paymentProvider.routingNumber=${establishData.beneficiaryAccount.paymentProvider.routingNumber}`;
85 if (establishData.beneficiaryAccount.paymentProvider.swift) query += `&beneficiaryAccount.paymentProvider.swift=${establishData.beneficiaryAccount.paymentProvider.swift}`;
86 if (establishData.beneficiaryAccount.paymentProvider.country) query += `&beneficiaryAccount.paymentProvider.country=${establishData.beneficiaryAccount.paymentProvider.country}`;
87 }
88 if (establishData.beneficiaryAccount.iban) query += `&beneficiaryAccount.iban=${establishData.beneficiaryAccount.iban}`;
89 }
90
91 if (establishData.transactionId) query += `&transactionId=${establishData.transactionId}`;
92
93 const requestSignature = Crypto.createHmac('sha1', accessKey).update(query).digest('base64');
94 return requestSignature;
95}
Optimized Javascript
const crypto = require('crypto');
// Ordered and final keys list
const signatureKeysOrdered = ['accessId', 'merchantId', 'description', 'currency', 'amount', 'displayAmount', 'minimumBalance', 'merchantReference', 'paymentType', 'timeZone', 'recurrence.startDate', 'recurrence.endDate', 'recurrence.frequency', 'recurrence.frequencyUnit', 'recurrence.frequencyUnitType', 'recurrence.recurringAmount', 'recurrence.automaticCapture', 'verification.status', 'verification.verifyCustomer', 'customer.customerId', 'customer.externalId', 'customer.name', 'customer.vip', 'customer.taxId', 'customer.driverLicense.number', 'customer.driverLicense.state', 'customer.address.address1', 'customer.address.address2', 'customer.address.city', 'customer.address.state', 'customer.address.zip', 'customer.address.country', 'customer.phone', 'customer.email', 'customer.balance', 'customer.currency', 'customer.enrollDate', 'customer.externalTier', 'customer.dateOfBirth', 'account.nameOnAccount', 'account.name', 'account.type', 'account.profile', 'account.accountNumber', 'account.routingNumber', 'transactionId'];
// Removed 'export' to ensure compatibility with standard Node.js scripts
const generateSignature = (establishData, accessKey) => {
const searchParams = [];
// Use only parameters present on the key list
for (const key of signatureKeysOrdered) {
// If the key doesn't have a child, push it now
if (!key.includes('.')) {
establishData[key] && searchParams.push(`${key}=${establishData[key].toString()}`);
continue;
}
// If the key has a child, split it into sub-keys
const subKeys = key.split('.');
let data = establishData;
// For each sub-key, set its value
for (const subKey of subKeys) {
const innerValue = data[subKey];
if (typeof innerValue === 'object') {
// If the sub-key has a sub-key, process it again
data = innerValue;
} else {
// If not, push it
innerValue && searchParams.push(`${key}=${innerValue.toString()}`);
break;
}
}
}
// Concatenate all parameters into a plain string
const query = searchParams.join('&');
// Encode the string
const requestSignature = crypto.createHmac('sha1', accessKey).update(query).digest('base64');
return requestSignature;
};
1public class TrustlySignature {
2
3 public static class Account { //Used only on account verification
4 public String nameOnAccount;
5 public String name;
6 public String type;
7 public String profile;
8 public String accountNumber;
9 public String routingNumber;
10 }
11
12 public static class Recurrence { //Used only when payment type is Recurring
13 public Date startDate;
14 public Date endDate;
15 public Integer frequency;
16 public Integer frequencyUnit;
17 public Integer frequencyUnitType;
18 public BigDecimal recurringAmount;
19 public Boolean automaticCapture;
20 }
21
22 public static class Address {
23 public String address1;
24 public String address2;
25 public String city;
26 public String state;
27 public String zip;
28 public String country;
29 }
30
31 public static class DriverLicense {
32 public String number;
33 public String state;
34 }
35
36 public static class Customer {
37 public String customerId; //Trustly customer ID
38 public String externalId; //Merchant’s customer ID
39 public String name;
40 public String vip;
41 public String taxId;
42 public DriverLicense dL;
43 public Address address;
44 public String phone;
45 public String email;
46 public String balance;
47 public String currency;
48 public String enrollDate;
49 public String dateOfBirth;
50 }
51
52 public static class Verification {
53 public String status;
54 public Boolean verifyCustomer;
55 }
56
57 public String createRequestSignature(String accessId,
58 String accessKey,
59 String merchantId,
60 String description,
61 String currency,
62 BigDecimal amount,
63 BigDecimal displayAmount,
64 BigDecimal minimumBalance,
65 String merchantReference,
66 String paymentType,
67 String timeZone,
68 Recurrence r,
69 Verification v,
70 Customer c,
71 Account a,
72 String transactionId) throws Exception {
73 String str = "accessId="+ accessId;
74 str += "&merchantId="+merchantId;
75 str += "&description="+description;
76 str += "&currency="+currency;
77 str += "&amount="+amount.setScale(2); //2 decimal places such as 13.25
78 if(displayAmount != null) str += "&displayAmount="+displayAmount.setScale(2); //2 decimal places such as 13.25
79 if(minimumBalance != null) str += "&minimumBalance="+minimumBalance.setScale(2); //2 decimal places such as 13.25
80 str += "&merchantReference="+merchantReference;
81 str += "&paymentType="+paymentType;
82 if(timeZone != null) str += "&timeZone="+timeZone;
83
84 if("Recurring".equals(paymentType) && r != null){
85 if(r.startDate != null) str += "&recurrence.startDate=" + r.startDate.getTime();
86 if(r.endDate != null) str += "&recurrence.endDate=" + r.endDate.getTime();
87 if(r.frequency != null) str += "&recurrence.frequency="+r.frequency;
88 if(r.frequencyUnit != null) str += "&recurrence.frequencyUnit="+r.frequencyUnit;
89 if(r.frequencyUnitType != null) str +="&recurrence.frequencyUnitType="+r.frequencyUnitType;
90 if(r.recurringAmount != null) str +="&recurrence.recurringAmount="+r.recurringAmount;
91 if(r.automaticCapture != null) str +="&recurrence.automaticCapture="+r.automaticCapture;
92 }
93
94 if (v != null) {
95 if(v.status != null) str +="&verification.status=" + v.status;
96 if(v.verifyCustomer != null) str +="&verification.verifyCustomer=" + v.verifyCustomer;
97 }
98
99 if(c != null) {
100 if(c.customerId != null) str +="&customer.customerId=" + c.customerId;
101 if(c.externalId != null) str +="&customer.externalId=" + c.externalId;
102 if(c.name != null) str +="&customer.name=" + c.name;
103 if(c.vip != null) str +="&customer.vip=" + c.vip;
104 if(c.taxId != null) str +="&customer.taxId=" + c.taxId;
105
106 if(c.dL != null) {
107 if(c.dL.number != null) str +="&customer.driverLicense.number=" + c.dL.number;
108 if(c.dL.state != null) str +="&customer.driverLicense.state=" + c.dL.state;
109 }
110
111 if(c.address != null) {
112 if(c.address.address1 != null) str +="&customer.address.address1=" + c.address.address1;
113 if(c.address.address2 != null) str +="&customer.address.address2=" + c.address.address2;
114 if(c.address.city != null) str +="&customer.address.city=" + c.address.city;
115 if(c.address.state != null) str +="&customer.address.state=" + c.address.state;
116 if(c.address.zip != null) str +="&customer.address.zip=" + c.address.zip;
117 if(c.address.country != null) str +="&customer.address.country=" + c.address.country;
118 }
119 if(c.phone != null) str +="&customer.phone=" + c.phone;
120 if(c.email != null) str +="&customer.email=" + c.email;
121 if(c.balance != null) str +="&customer.balance=" + c.balance.setScale(2);
122 if(c.currency != null) str +="&customer.currency=" + c.currency;
123 if(c.enrollDate != null) str +="&customer.enrollDate=" + c.enrollDate;
124 if(c.dateOfBirth != null) str +="&customer.dateOfBirth=" + c.dateOfBirth;
125 }
126
127 if(a != null) {
128 if(a.nameOnAccount != null) str +="account.nameOnAccount=" + a.nameOnAccount;
129 if(a.name != null) str +="account.name=" + a.name;
130 if(a.type != null) str +="account.type=" + a.type;
131 if(a.accountNumber!= null) str +="account.accountNumber=" + a.accountNumber;
132 if(a.routingNumber!= null) str +="account.routingNumber=" + a.routingNumber;
133 }
134
135 if(transactionId != null) str += "&transactionId="+transactionId;
136
137 return TrustlySignature.doHashing(str,accessKey);
138 }
139
140 public static String doHashing(String str, String accessKey) throws Exception {
141 Mac = Mac.getInstance("HmacSHA1");
142 SecretKeySpec signingKey = new SecretKeySpec(accessKey.getBytes(),"HmacSHA1");
143 mac.init(signingKey);
144 byte[] rawHmac = mac.doFinal(str.getBytes());
145 return encodeBase64(rawHmac);
146 }
147
148 public static String encodeBase64(byte[] binary){
149 return DatatypeConverter.printBase64Binary(binary);
150 }
151}