Appmax JS
What is Appmax JS
appmax.js is a JavaScript library developed by Appmax for secure integration on checkout pages. By including the script, you don't need to worry about changes to the store's appearance, since it operates discreetly and efficiently.
IP collection is always mandatory
IP collection via Appmax JS is mandatory for all integrations — even if your architecture is within PCI-DSS scope. There is no API alternative for this step alone.
Server-side tokenization requires PCI-DSS
Card tokenization, on the other hand, can be done in two ways: through Appmax JS itself (recommended — the script isolates sensitive data from your servers) or directly through your API, if your architecture is within PCI-DSS scope. When tokenizing from your backend, your server touches the card number and CVV in the clear — this is only allowed within PCI-DSS scope. If you're not sure, use the Appmax JS path. See Credit card tokenization.
How it works
Due to PCI DSS (Payment Card Industry Data Security Standard) guidelines, it is crucial to protect sensitive data. appmax.js was designed to:
- Prevent sensitive card data from passing through your servers.
- Collect the customer's IP address for payment flow security.
How to use
<script src="https://scripts.appmax.com.br/appmax.min.js"></script>After loading the script, initialize it with the parameters below:
window.AppmaxScripts.init(onSuccess, onError, externalId, onUpdate, onAuthorize);| Parameter | Required | Description |
|---|---|---|
onSuccess | Yes | Success callback. Receives { ip, token? } after IP collection or tokenization. |
onError | Yes | Error callback. Receives the error thrown by the script. |
externalId | Yes for tokenization and Apple Pay | App installation identifier for the store. See external-id. |
onUpdate | Yes for Apple Pay | Callback fired when the PaymentSheet opens and on every change to it. Must return the current cart, with numeric values in BRL — see the return contract. |
onAuthorize | Yes for Apple Pay | Callback fired when the payment is authorized. Receives the Apple Token. Reject the Promise to signal failure — see how to signal failure. |
Implementing the Apple Pay button?
onUpdate and onAuthorize are only half the story — the button also requires specific DOM selectors and its own load order. See the dedicated walkthrough at Implementing the Apple Pay button with the Appmax JS.
externalId is required for tokenization through Appmax JS
Without externalId, submitting any form with data-appmax-checkout fails with External ID is required before the HTTP call is made. The value is the same external_id defined during the app installation at the store — persist it in your database and render it in the checkout template. See external-id for the full reference.
If your architecture is within PCI-DSS scope and you'd rather tokenize directly through your API (without the script), use the backend path with the merchant's access token (Authorization: Bearer) instead of externalId — see Credit card tokenization.
init() can throw a synchronous exception for Apple Pay
If you pass onUpdate and onAuthorize (Apple Pay flow) without a valid externalId, init throws Error("External ID is required for Apple Pay use.") synchronously, inside the call itself — not via onError. In React, this blows up inside useEffect and can bring down the whole tree if there's no try/catch around the call.
If you only need IP collection (no tokenization, no Apple Pay), externalId, onUpdate, and onAuthorize can be omitted:
window.AppmaxScripts.init(onSuccess, onError);DOM contract: init is not reactive
AppmaxScripts.init(...) runs querySelector once, at the moment it's called, and doesn't observe DOM changes after that. On a traditional page (server-rendered HTML, nothing changes after load) this is transparent. In React, Vue, or any SPA, it needs attention:
- The IP trigger and the Apple Pay button need to exist in the DOM before
initruns. If the element shows up later — behind a route, a checkout step, av-if/conditional — the SDK never finds it. There's no error, no log: the click or the collection simply don't happen. init()is not idempotent. Each call registers new listeners, without removing the previous ones. With React StrictMode (which mounts effects twice in development) or any component that re-renders and re-runs the initialization effect, this silently accumulates handlers. Callinitonce per page load, not on every re-render.
Available features
Customer IP collection
Collection happens when the SDK finds one of the triggers below in the DOM — no need to submit anything or wait for user interaction. Without one of the two present at init time, onSuccess and onError simply don't fire (see "DOM contract" above — it's a silent failure, no error at all).
| Trigger | When to use |
|---|---|
form[data-appmax-customer] | Traditional page (MPA), native form. |
.appmax-ip (any element) | Recommended for SPAs — doesn't require a <form> in the tree and avoids the hidden-input injection described in the warning below. |
<form id="customer-form" data-appmax-customer>
<div>
<label for="first-name">First Name:</label>
<input type="text" id="first-name" name="first-name" required />
</div>
<div>
<label for="last-name">Last Name:</label>
<input type="text" id="last-name" name="last-name" required />
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<div>
<label for="phone">Phone:</label>
<input type="text" id="phone" name="phone" required />
</div>
<button type="submit">Submit</button>
</form>Or, with no form at all — the element just needs to exist, not be visible:
<span class="appmax-ip" hidden></span>The SDK injects a hidden <input> inside form[data-appmax-customer]
When it finds that form, the SDK inserts an <input type="hidden" id="ip" name="ip"> into it via direct DOM manipulation — outside your framework's control. In an SPA, that node can be dropped on the next re-render with no warning. If you're in an SPA, prefer the .appmax-ip trigger above, which doesn't have this problem.
For frameworks like Vue.js, the trigger (.appmax-ip in the example below) needs to be rendered before init — see "DOM contract" above. Retrieve the IP in the success callback:
<template>
<span class="appmax-ip" hidden></span>
</template>const customer = ref({
first_name: '',
last_name: '',
email: '',
phone: '',
ip: ''
})
const success = (data) => {
customer.value.ip = data.ip || 'IP not found.'
}
const error = (error) => {
console.error('Error:', error)
}
onMounted(() => {
const script = document.createElement('script')
script.src = 'https://scripts.appmax.com.br/appmax.min.js'
script.onload = () => {
if (window.AppmaxScripts) {
window.AppmaxScripts.init(success, error)
}
}
document.head.appendChild(script)
})Payment tokenization
Tokenization occurs when a form is submitted with the data-appmax-checkout attribute. Sensitive card data is converted into a secure token.
Requires externalId at initialization
To tokenize through the script, initialize with the store's externalId: AppmaxScripts.init(onSuccess, onError, externalId). Without it, the form submit fails with External ID is required. See external-id.
The token arrives back via the onSuccess({ ip, token }) callback — you don't touch HTTP or headers. If you need to tokenize manually (without the script), the underlying endpoint contract lives at Credit card tokenization — including the alternative backend path (merchant Bearer), allowed only within PCI-DSS scope.
<form id="payment-form" method="POST" data-appmax-checkout>
<div>
<label for="card-number">Card Number:</label>
<input type="text" id="card-number" name="card-number"
appmax-form-element="number" required />
</div>
<div>
<label for="card-holder-name">Cardholder Name:</label>
<input type="text" id="card-holder-name" name="card-holder-name"
appmax-form-element="holder_name" required />
</div>
<div>
<label for="exp-month">Expiration Month:</label>
<input type="text" id="exp-month" name="exp-month"
appmax-form-element="expiration_month" required />
</div>
<div>
<label for="exp-year">Expiration Year:</label>
<input type="text" id="exp-year" name="exp-year"
appmax-form-element="expiration_year" required />
</div>
<div>
<label for="cvv">CVV:</label>
<input type="text" id="cvv" name="cvv"
appmax-form-element="cvv" required />
</div>
<button type="submit">Pay</button>
</form>Fields are identified by the appmax-form-element attribute:
| Attribute | Description |
|---|---|
number | Credit card number |
holder_name | Cardholder name |
expiration_month | Expiration month |
expiration_year | Expiration year |
cvv | Security code (CVV) |
Interactive test
Use the playground below to test the CDN features directly in the browser — no installation required.