Skip to content

Backend Setup

Your server is responsible for obtaining an access token from Zatlas and passing it to the frontend. The SDK uses this token for all API calls inside the iframe.

Your server sends a client_credentials grant to the Zatlas OAuth2 endpoint:

EnvironmentURL
Sandboxhttps://cde-api-staging.zatlas.com/oauth2/token
Productionhttps://cde-api.zatlas.com/oauth2/token
routes/payment-token.js
import express from 'express';
const router = express.Router();
router.get('/api/payment-token', async (req, res) => {
const response = await fetch(process.env.ZATLAS_TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.ZATLAS_CLIENT_ID,
client_secret: process.env.ZATLAS_CLIENT_SECRET,
}),
});
const data = await response.json();
res.json({ accessToken: data.access_token });
});
export default router;
routes/payment_token.py
import os
import requests
from flask import Blueprint, jsonify
bp = Blueprint('payment_token', __name__)
@bp.route('/api/payment-token')
def get_token():
response = requests.post(
os.environ['ZATLAS_TOKEN_URL'],
data={
'grant_type': 'client_credentials',
'client_id': os.environ['ZATLAS_CLIENT_ID'],
'client_secret': os.environ['ZATLAS_CLIENT_SECRET'],
},
)
data = response.json()
return jsonify(accessToken=data['access_token'])
PaymentTokenController.java
@RestController
public class PaymentTokenController {
@Value("${zatlas.token-url}") private String tokenUrl;
@Value("${zatlas.client-id}") private String clientId;
@Value("${zatlas.client-secret}") private String clientSecret;
@GetMapping("/api/payment-token")
public Map<String, String> getToken() {
var client = HttpClient.newHttpClient();
var body = "grant_type=client_credentials"
+ "&client_id=" + clientId
+ "&client_secret=" + clientSecret;
var request = HttpRequest.newBuilder()
.uri(URI.create(tokenUrl))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
var json = new ObjectMapper().readTree(response.body());
return Map.of("accessToken", json.get("access_token").asText());
}
}
PaymentTokenController.kt
@RestController
class PaymentTokenController(
@Value("\${zatlas.token-url}") private val tokenUrl: String,
@Value("\${zatlas.client-id}") private val clientId: String,
@Value("\${zatlas.client-secret}") private val clientSecret: String,
) {
private val webClient = WebClient.create()
@GetMapping("/api/payment-token")
suspend fun getToken(): Map<String, String> {
val response = webClient.post()
.uri(tokenUrl)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.bodyValue("grant_type=client_credentials&client_id=$clientId&client_secret=$clientSecret")
.retrieve()
.awaitBody<Map<String, Any>>()
return mapOf("accessToken" to response["access_token"].toString())
}
}

Set these on your server (never in frontend code):

VariableExampleDescription
ZATLAS_TOKEN_URLhttps://cde-api-staging.zatlas.com/oauth2/tokenOAuth2 token endpoint
ZATLAS_CLIENT_ID3abc123def456...OAuth2 client ID
ZATLAS_CLIENT_SECRETsecret-xyz-789...OAuth2 client secret

Access tokens are valid for 1 hour (expires_in: 3600). To avoid unnecessary round-trips, cache the token and refresh it before it expires:

lib/zatlas-token.js
let cached = { token: null, expiresAt: 0 };
export async function getAccessToken() {
const now = Date.now();
// Refresh 60 seconds before expiry
if (cached.token && cached.expiresAt > now + 60_000) {
return cached.token;
}
const response = await fetch(process.env.ZATLAS_TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.ZATLAS_CLIENT_ID,
client_secret: process.env.ZATLAS_CLIENT_SECRET,
}),
});
const data = await response.json();
cached = {
token: data.access_token,
expiresAt: now + data.expires_in * 1000,
};
return cached.token;
}