JWTs
The API uses Bearer (Token) Authentication to authenticate any request. These tokens are JSON Web Tokens (JWT) which need to be created server side by your application. By far the easiest way to create a JWT is with one of our server-side SDKs but it is also possible to generate the JWT without our SDKs using any number of open source JWT libraries.
This document defines our API specification for authentication, JWT claims, and signature mechanisms.
Authenticate an API call
Every API endpoint that requires authentication expects an authorization
HTTP-header with a signed JWT token as its value (prefixed with bearer
).
curl -i -X GET "https://api.efundpay.com/v4/payment" \
-H "authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
Create an API key
To sign a JWT you will need to create a new API key-pair in your dashboard.
To create a new API key visit the Integrations panel in your dashboard and click the Add API key button. You can store the API key with your code or store it in a secure environment accessible to your application.
Merchants access
When selecting an API key, if your instance has Multi Merchant enabled then you can select whether the API key can access All merchants or only one. We recommend you restrict an API key to only one merchant account when integrating into Embed, our SDKs, or our e-commerce platforms like Magento, Salesforce Commerce Cloud, and Commerce Tools.
Permissions
When selecting an API key, you can select whether to generate a key that has access to all APIs within the merchants it has access to (Full access) or only to APIs used for processing payments (Processing only). We recommend you use the Processing only for any API keys with our e-commerce platforms like Magento, Salesforce Commerce Cloud, and Commerce Tools.
API access can be further reduced by setting a restrictive set of scopes on the JWT.
Algorithm
Our API supports 2 algorithms for signing JWTs: ECDSA and RSA. We recommend you use ECDSA unless your environment does not support this stronger and newer algorithm. The RSA algorithm is available for those environments that do not support ECDSA. RSA keys provide considerably less security for the same key size and are currently not supported by our SDKs.
Generate JWT
You might not want to use one of our SDKs, or an SDK in your language might not be available. In those cases you can construct, and sign the JWT with one of the many libraries available on jwt.io.
At the high level a JWT is build up out of 3 pieces:
- A header defining the algorithm and key used to create the JWT.
- A set of claims that define the token's scope and other permissions.
- A cryptographic signature based on the header and the claims, signed using your private key.
Combine, these 3 pieces make up the JSON Web Token (JWT). See jwt.io for more details on the specification and available libraries for generating JWTs.
JWT header
The JWT header defines the type of encryption algorithm as well as the private key used to generate the signature.
{
"typ": "JWT",
"alg": "ES512", // OR RS512 depending on your key
"kid": "d757c76acbd74b56"
}
The typ
and alg
are fixed and do not allow for other values. The kid
is the ID of your private key, which you can find in the Integrations panel of your dashboard.
JWT claims
The claims define when the token was created and what access it has.
{
"iss": "My JWT Generation Tool",
"nbf": 1607976645,
"exp": 1607977245,
"jti": "0fe1fb1b-2f7e-4c8d-b0eb-aae5d0ec98f7",
"scopes": ["transactions.read"],
"embed": {
"amount": "200",
"currency": "AUD",
"buyer_id": "d757c76a-cbd7-4b56-95a3-40125b51b29c",
"metadata": { "key": "value" },
"cart_items": [
{
"name": "Joust Duffle Bag",
"quantity": "1",
"unit_amount": "9000",
"tax_amount": "0"
}
]
}
}
Claims
The API supports the following JWT claims.
Field | Description | Required |
---|---|---|
iss | A unique ID that represents your code making this call. This helps identify what library made an API call to EFundFlow. | Yes |
nbf | The UNIX timestamp (in seconds) that this token was created at. | Yes |
exp | The UNIX timestamp (in seconds) that this token expires at. | Yes |
iat | An optional UNIX timestamp (in seconds) for your internal use to indicate when the token was issued. | No |
jti | A random unique ID used for cryptographic entropy. This needs to be unique for each JWT. | Yes |
scopes | A list of scopes that give this token access to the API. | Yes |
embed | A dictionary of key-value pairs used to pin the amount, currency, and buyer info for use in Embed. | No |
checkout_session_id | The ID of a checkout session. This can be used to tie multiple transactions together as having originated from the same session. | No |
merchantId | The merchant unique identifier. Used to specify which merchant the JWT is issued for. | No |
Timestamps
Please be aware that the nbf
, exp
, and iat
values are UNIX timestamps defined as seconds since January 1st, 1970 (UTC). Some programming languages will return UNIX timestamps as milliseconds, requiring the removal of the last 3 digits.
Scopes
The API supports the following values for the scopes
claims.
Scope | Description |
---|---|
*.read | Allows read-access to any resource. This is used by default in the SDKs |
*.write | Allows write-access to any resource. This is used by default in the SDKs. This does not also allow read access. |
{resource_name} .read | Allows read-access to a type or resource. For example, payment-services.read enabled read-access for buyers data. |
{resource_name} .write | Allows write-access to a type or resource. For example, payment-services.write enabled write-access for buyers data. This does not also allow read access. |
embed | A scope that represents all the access needed by Embed. |
The following resource names are recognized. Please see the reference documentation for more details as to what scope is required per endpoint.
anti-fraud-services
api-logs
buyers
buyers.billing-details
card-scheme-definitions
checkout-sessions
connections
digital-wallets
flows
payment-methods
payment-method-definitions
payment-options
payment-service-definitions
payment-services
reports
transactions
Signature & assembly
Finally, the JWT signature is generated by appending the Base64 encoded header and claims (separated with a .
) and run it through the key's algorithm.
ECDSA
ECDSASHA512(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
public_key,
private_key
)
The assembled JWT is then formed by appending the Base64 encoded header, claims, and signature separated by a full stop.
base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + base64UrlEncode(signature)
Code Example
import java.security.PrivateKey;
import java.util.*;
import java.time.Instant;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyFactory;
private static String createValidJwtToken() {
try {
String TEST_RSA_PRIVATE_KEY_BASE64 = "MIIEvg....UGu5Rp8";
String alg = "RS512";
String header = String.format("{\"typ\":\"JWT\",\"alg\":\"%s\",\"kid\":\"%s\"}", alg, TEST_SUB_MERCHANT_ID);
String encodedHeader = base64UrlEncode(header);
long currentTime = Instant.now().getEpochSecond();
String payload = String.format(
"{\"iss\":\"efundflow.com\",\"nbf\":%d,\"exp\":%d,\"jti\":\"test-jwt-id\",\"scopes\":[\"transactions.read\"]}",
currentTime - 60, currentTime + 3600
);
String encodedPayload = base64UrlEncode(payload);;
PrivateKey privateKey = loadRSAPrivateKey(TEST_RSA_PRIVATE_KEY_BASE64);
String signature = generateRS512Signature(header, payload, privateKey);
return encodedHeader + "." + encodedPayload + "." + signature;
} catch (Exception e) {
LOGGER.error("Failed to create test JWT token", e);
return "invalid.token.for.testing";
}
}
private static PrivateKey loadRSAPrivateKey(String base64Key) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
private static String generateRS512Signature(String header, String payload, PrivateKey privateKey) throws Exception {
String data = base64UrlEncode(header) + "." + base64UrlEncode(payload);
Signature signature = Signature.getInstance("SHA512withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return base64UrlEncode(signBytes);
}
private static String base64UrlEncode(String str) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(str.getBytes());
}