Integration steps
How the Agoda partner.js solution works
partner.js solution worksThe partner.js solution enables partners to associate each booking attempt with a verified set of frontend session and device context signals. It supports fraud analysis, risk assessment, and performs 3DS execution when required, and optionally tokenizes card details. Partner-SDK generates a sdk_context_token which must be passed unchanged in the Affiliate Book API as part of the booking request.
In summary, the integration consists of three steps, followed by a conditional step for 3DS challenge flow. Refer to the detailed step-by-step instructions in subsections to learn more.
-
Include Partner-SDK on your website: You must include Partner-SDK on the checkout or booking page at a minimum. For optimal session continuity and stronger fraud detection, we recommend including Partner-SDK on every page of the booking journey through checkout, starting with the home or landing page when possible.
-
Initialize a session in the browser: After Partner-SDK is loaded, you must initialize it to start or resume a session. During this step, Partner-SDK collects eligible browser and device context signals in the background. Partner-SDK supports common partner security configurations, including Content Security Policy constraints.
-
Generate and submit the
sdk_context_tokenwith your Book request: Before submitting the booking to your backend, you must invoke the SDK checkout method to generate thesdk_context_token. Your frontend must forward the obtainedsdk_context_tokento your backend, and your backend must pass it unchanged in the Book API request. -
[Conditional] Complete the 3DS challenge when required: If the Book API response
statusreturns 202, and therequiredActionindicatespayment_challenge, pass thestatusTokenback to the client browser, and have your website callwindow.partnerSDK.handleChallenge(statusToken)to complete the 3DS challenge. Your backend must also poll the Booking Status API to obtain the final booking status.
Integration flow
Follow the steps below to integrate the Partner-SDK into your website and enable token generation for fraud detection.
Step 1 - Installation & setup
Load partner.js on your home and checkout pages at minimum.
- Required: Checkout page (where the booking is submitted)
- Recommended: Every page of the user's booking journey (to establish the user session early) through checkout, beginning with the home or landing page when possible (where the user starts their booking journey)
- (Global - Preferred): Add the script to your base layout/template. This ensures the SDK and its security beacons load and are ready before the user transitions to the payment page.
Example:
Place the following script in the <head> or before the closing <body>tag.
<script src="https://cdn6.agoda.net/s-cdn-partner-sdk/v1/`partner.js`"></script>The Partner SDK (partner.js) may dynamically load additional scripts and send browser beacons used for fraud detection and telemetry. To ensure full functionality (and avoid blocking required network calls), update your site’s Content Security Policy (CSP) to explicitly allow the SDK CDN, fraud vendor endpoints, and the Bento analytics endpoints provided during onboarding.
Optional CSP directives and example values
| Directive | Purpose / Description | Allowed Sources / Notes |
|---|---|---|
| script-src | Allow loading of partner.js and any secondary script loaders/beacons | - Agoda SDK CDN: https://cdn6.agoda.com - Fraud provider beacon/loader: https://beacon.riskified.com, https://*.forter.com, https://*.cloudfront.net - Bento analytics scripts: https://bento.agoda.com - If you use nonces: Include 'nonce-YOUR_NONCE_VALUE' for each script tag that uses a nonce. |
| connect-src | Allow XHR/fetch/beacon network calls that the SDK will make | - Partner SDK backend endpoints: https://*.agoda.com and https://*.travelwebservice.com - Fraud provider endpoints: https://beacon.riskified.com, https://*.forter.com, https://*.cloudfront.net - Bento analytics collection endpoint(s): (https://bento.agoda.com/) (covered with https://*.agoda.com) |
| worker-src | Allow telemetry | - Agoda telemetry: https://www.agoda.com |
| img-src | Allow pixel image beacons (if used by fraud/analytics vendors) | - Fraud provider image pixel: https://img.riskified.com |
Example CSP header:
<meta http-equiv="Content-Security-Policy" content="
script-src
'self'
https://www.agoda.com
https://*.agoda.com
https://*.riskified.com
https://cdn6.agoda.net
https://*.forter.com
https://*.cloudfront.net;
connect-src
'self'
https://*.agoda.com
https://*.travelwebservice.com
https://bento.agoda.com
https://*.riskified.com
https://*.forter.com
https://*.cloudfront.net
https://1.1.1.1
https://wtp.siteperformancetest.net
https://siteperformancetest.net
wss://cdn0.forter.com;
worker-src
'self'
https://www.agoda.com
https://*.agoda.com
img-src
'self'
https://img.riskified.com;
">
Step 2 — Initialize a session in the browser
After the script loads, call window.partnerSDK.init(config) to initialize a browser session and load the required fraud-detection components.
During initialization, the SDK collects eligible browser, session, and device context signals in the background. The SDK is designed to work with common partner security setups (including Content Security Policy (CSP) constraints).
- Call
window.partnerSDK.init(config)with the requiredsiteIdand environment values. - To enable PAN tokenization, provide the exact
partnerNamesupplied by Agoda. Theinit()validates the format ofpartnerNameand rejects invalid syntax; it does not confirm that the name is configured for tokenization. - You may provide
cspNonce,onReady, andonErrorwhen needed.
window.partnerSDK.init(config: {
siteId: string;
partnerName?: string;
environment: 'sandbox' | 'production';
cspNonce?: string;
onReady?: () => void;
onError?: (error: Error) => void;
}): Promise<void>;Partner SDK init parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteId | string | Required | Unique identifier provided by Agoda to the partner |
partnerName | string | Provisional | Optional when PAN tokenization is not required; required to enable PAN tokenization. Use the exact partnerName supplied by Agoda. The value must contain lowercase letters, digits, or hyphens and must start and end with a letter or digit. Invalid syntax causes init() to reject. A syntactically valid name can pass init() even if it is not configured for tokenization; checkout() rejects with TOKENIZE_FAILED if tokenization cannot be completed. |
environment | enum | Required | Environment to use ("sandbox" or "production") |
cspNonce | string | Optional | Allows safe, authorized inline scripts/styles |
onReady | function | Optional | Hook to enable UI elements once the SDK is "live" |
onError | function | Optional | Hook to catch network or configuration errors |
- You can handle the init result using either Promise or a Callback.
Option A: Using the Promise response (recommended)
Assign the response to a variable to access the token directly.
try {
await window.partnerSDK.init({
siteId: "12345", // Agoda-issued site ID
partnerName: "partner-test", // Optional Agoda-issued partner name; provide to enable PAN tokenization
environment: "sandbox", // Use production only when ready
cspNonce: "rAnd0m12345", // Optional nonce for CSP compliance
});
console.log("SDK is ready") // Optional log for Init success
} catch(e) {
// SDK initialization fail/error handling
console.error("SDK init failed", error) // Optional log for Init fail
}Option B: Using a Callback
<script>
window.partnerSDK.init({
siteId: "12345", // Agoda-issued site ID
partnerName: "partner-test", // Optional Agoda-issued partner name; provide to enable PAN tokenization
environment: "sandbox", // Use production only when ready
cspNonce: "rAnd0m12345", // Optional nonce for CSP compliance
onReady: () => console.log("SDK is ready"), // optional if used as promise
onError: (error) => console.error("SDK init failed", error)
});
</script>
- The
init()completes asynchronously. When initialization succeeds, Partner-SDK is ready to use. If the configuration is invalid, initialization fails and the Promise/Callback returned byinit()is rejected. Fraud-beacon issues do not stop initialization and are reported throughonError.
Step 3 — Generate a sdk_context_token with checkout()
sdk_context_token with checkout()The Book API request requires the sdk_context_token value generated by the checkout() method. Therefore, before submitting the booking from your frontend, you must invoke the SDK checkout() method.
- The
checkout()resolves on success with{ success: true, sdk_context_token }. Forward the generatedsdk_context_tokenunchanged to the partner backend. - Any checkout failure rejects with
{ success: false, error: { code, message } }. Handle the rejection withtry/catchand do not submit the booking.
Configuration parameters
type PaymentCardDetails = {
creditCardNumber: string;
cardHolderName: string;
expiryDate: string;
cvc: string;
};
type CheckoutErrorCode =
| 'INVALID_CONFIG'
| 'TOKENIZE_FAILED';
type CheckoutError = {
code: CheckoutErrorCode;
message: string;
};
type CheckoutSuccessResult = {
success: true;
sdk_context_token: string;
};
type CheckoutFailureResult = {
success: false;
error: CheckoutError;
};
type CheckoutCallback = (
result: CheckoutSuccessResult | undefined,
error?: CheckoutError,
) => void;
window.partnerSDK.checkout(
details?: Partial<PaymentCardDetails>,
cb?: CheckoutCallback,
): Promise<CheckoutSuccessResult>;
SDK checkout parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
cardDetails | Partial<PaymentCardDetails> | Conditional | An object containing the sensitive payment information (number, expiry, CVV). When partnerName is configured, all four card fields are required; missing or incomplete fields cause checkout() to reject with TOKENIZE_FAILED. When partnerName is not configured (or when the partnerName is not syntactically valid ), omit this value to skip tokenization; checkout() can still return sdk_context_token without tokenized card information. |
callback | CheckoutCallback | Optional | A function triggered upon completion, providing the CheckoutResult to the caller. An optional notification callback. It receives the success result as its first argument or the checkout error as its second argument. |
PaymentCardDetails
| Parameter | Type | Required | Description |
|---|---|---|---|
creditCardNumber | string | Required | The credit card number will consist of 15 or 16 digits. Each digit can be 0-9 |
expiryDate | string | Required | Expiry Date in the format MMYYYY. Each digit can be 0-9 |
cvc | string | Required | CVC code consisting of 3-4 digits. Each digit can be 0-9 |
cardHolderName | string | Required | Cardholder name |
CheckoutSuccessResult and CheckoutFailureResult
| Parameter | Type | Description |
|---|---|---|
success | boolean | Indicates if the verification was successful. Returns true on a fulfilled CheckoutSuccessResult; false on a rejected CheckoutFailureResult. |
sdk_context_token | string | Conditional. Only present if success is true (fulfilled CheckoutSuccessResult). This is the opaque verification token that must be passed to your backend. |
error | object | Conditional. Only present if success is false (rejected CheckoutFailureResult). Contains error details. |
error.code | CheckoutErrorCode | A machine-readable error identifier (e.g., FRAUD_FAIL, NETWORK_FAILURE). Use this value to determine how to handle the failure (see the table below for error codes). |
error.message | string | A human-readable description of the error for logging purposes. |
CheckoutErrorCode
- When
checkout()rejects, you must handle the failure and stop the booking submission in your frontend implementation.
| Error code | When is it returned | Partner frontend action |
|---|---|---|
INVALID_CONFIG | checkout() was called before init(), or the stored SDK configuration is invalid. | Treat this as an SDK-integration configuration issue. Do not submit the booking. Show a generic technical error and log the details; the customer is not expected to correct this issue. |
TOKENIZE_FAILED | This rejection occurs only when PAN tokenization is enabled. The SDK was expected to tokenize the card but could not complete tokenization. This includes missing or invalid card details, card-validation failures, and tokenization request failures. No sdk_context_token is returned. | Do not submit the booking. Suggest that the customer review their card details and try again. If the issue persists, show a generic error and offer a retry. |
Usage
- For the
checkout()method, first prepare your payment card details object.
// First, prepare your cardDetails object:
// partnerName in init() enables PAN tokenization;
// when enabled, all four fields below are required.
const cardDetails = {
creditCardNumber: "4111...", // Full card number
cardHolderName: "John Doe",
expiryDate: "122028", // MMYYYY format
cvc: "123"
};
- The
checkout()returns a Promise. Callbacks are optional notifications and do not consume or replace the Promise. The Promise resolves on success and rejects on failure. - You can handle the checkout result using either the Promise or Callback.
Option A: Using the Promise response (recommended)
- Assign the response to a variable to access the token directly.
try {
const response = await window.partnerSDK.checkout(cardDetails);
await submitBookingToBackend(response.sdk_context_token);
} catch (failure) {
handleCheckoutError(
failure.error.code,
failure.error.message,
);
}
Option B: Using a Callback
- Pass a callback function as the second argument.
const callback = (result, error) => {
if (result) {
console.log("Token received:", result.sdk_context_token);
// Proceed with your backend submission logic
} else if (error) {
console.error(`Error [${error.code}]: ${error.message}`);
}
};
window.partnerSDK.checkout(cardDetails, callback).catch(() => {
// The callback already handled the checkout failure.
});
Important:The
sdk_context_tokenis an opaque string. Your backend must include it unchanged in the Affiliate Book API request to ensure the transaction is correctly validated and attributed
Step 4 — Pass the sdk_context_token to backend
sdk_context_token to backendSend the sdk_context_token to your backend to be passed in the Book API as part of your booking request payload.
- Treat the token as opaque: do not decode, parse, or modify.
- Do not persist in any storage: not even temporarily (avoid localStorage, sessionStorage, cookies, or in-memory caches).
- Avoid logging the token. If logging is unavoidable, mask it.
Step 5 — Send sdk_context_token and customer details to Book API
sdk_context_token and customer details to Book API- Pass the
sdk_context_tokenreceived from SDK in the Book API request. - Pass raw credit card details only if PAN tokenization is not implemented in your integration, i.e., if SDK
checkout()method is not being used to tokenize the card, include the raw cardNumber, expiryDate, and cvc fields in the Book API request.
To ensure better fraud detection/screening accuracy, pass the customer's IP address, customer email address, customer phone number, and customer billing address details to the Book API.
{
"waitTime": 120,
"bookingDetails": {
"userCountry": "US",
"searchId": 1629183207719830000,
"tag": "00000000-0000-0000-0000-000000000000",
"allowDuplication": false,
"checkIn": "2022-12-23",
"checkOut": "2022-12-24",
"property": {
"propertyId": 12157,
"rooms": [
{
"blockId": "blockid_v3",
"rate": {
"inclusive": 798.58
},
"surcharges": [
{
"id": 278,
"rate": {
"inclusive": 12
}
},
{
"id": 279,
"rate": {
"inclusive": 12
}
}
],
"guestDetails": [
{
"title": "Mr.",
"firstName": "John",
"lastName": "Doe",
"countryOfResidence": "US",
"gender": "Male",
"age": 30,
"primary": true
}
],
"currency": "USD",
"paymentModel": "Merchant",
"count": 1,
"adults": 2,
"children": 2,
"childrenAges": [
5,
6
],
"specialRequest": "high floor"
}
]
}
},
"customerDetail": {
"language": "en-us",
"title": "Mr.",
"firstName": "Test",
"lastName": "Test",
"email": "[email protected]",
"phone": {
"countryCode": "66",
"areaCode": "2",
"number": "22222222222"
},
"newsletter": false,
"ipAddress": "<customer IP Address>"
},
"paymentDetails": {
"contextToken": "<sdk_context_token>",
"creditCardInfo": {
"number": "<PAN>", // Not required if PAN Tokenization is enabled
"expiryDate": "<EXPIRY (MMYYYY)>", // Not required if PAN Tokenization is enabled
"cvc": "<CVC>", // Not required if PAN Tokenization is enabled
"holderName": "<CARD HOLDER NAME>", // Not required if PAN Tokenization is enabled
"billingAddress": {
"addressLine1":"123 Main St",
"addressLine2": "Apt 4B",
"city": "New York",
"state": "New York",
"postalCode": "10001",
"countryCode": "US"
}
}
}
}Schema fields (highlights for payment/fraud handling)
| Parameter | Type | Description |
|---|---|---|
paymentDetails.contextToken | string | The token (sdk_context_token) generated from the SDK. |
paymentDetails.creditCardInfo | object | The raw credit card details (only pass if PAN tokenization is not implemented). |
paymentDetails.creditCardInfo.billingAddress | object | Customer billing address. Recommended to ensure better fraud detection/screening accuracy. |
customerDetail.ipAddress | string | The customer IP Address. Recommended to ensure better fraud detection/screening accuracy. |
customerDetail.email | string | Customer email address. Recommended to ensure better fraud detection/screening accuracy. |
customerDetail.phone | object | Customer phone number. Recommended to ensure better fraud detection/screening accuracy. |
- Refer to the Book API documentation to learn more about the Book API.
Step 6 — Book API response
Standard response (success/error)
- For standard booking responses (success or error), follow the existing flow. Refer to the Book API documentation to learn more.
- Example of a success response:
{
"status": "200",
"bookingDetails": [
{
"id": 20440123434,
"itineraryID": 20437611123,
"processing": false
}
]
}Challenge-required response (3DS)
- When a booking requires a 3DS challenge, the Book API returns
status: 202withrequiredActionset topayment_challenge. - In this case, pass the provided
statusTokenfrom Book response to the Partner SDK, and keep thestatusTokenon your backend for booking status polling. - Example:
{
"status": 202,
"bookingDetails": [],
"requiredAction": "payment_challenge",
"statusToken": "u9JX7vK8mW1NqA5zPz9wL3kM1bC6rX8zY4tV2bN9oE"
}Step 7 (conditional) — Handle a challenge-required booking
Note:
statusTokenandsdk_context_tokenhave different purposes. Pass thestatusTokentohandleChallenge(), and use the samestatusTokenon your backend to poll the Booking Status API. Never passsdk_context_tokenor any status value tohandleChallenge().
- If you received a challenge-required response in step 6, pass that exact
statusTokenprovided in the Book API response to your frontend application. - Have your frontend application call
handleChallenge()from the customer's browser with thestatusToken.
Important:The
statusTokenis an opaque string. Do not decode, parse, modify, log, construct, or store it in browser storage.
handleChallenge() method
type HandleChallengeFailureReason =
| 'failed'
| 'timeout'
| 'cancelled'
| 'mount_error'
| 'network_error'
| 'context_error'
| 'page_changed'
| 'decode_failed';
type HandleChallengeSuccessResult = {
success: true;
};
type HandleChallengeFailureResult = {
success: false;
reason: HandleChallengeFailureReason;
error?: Error;
};
type HandleChallengeOptions = {
container?: string | HTMLElement;
onSuccess?: () => void;
onFailure?: (failure: {
reason: HandleChallengeFailureReason;
error?: Error;
}) => void;
};
window.partnerSDK.handleChallenge(
statusToken: string,
options?: HandleChallengeOptions,
): Promise<HandleChallengeSuccessResult>;handleChallenge() parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
statusToken | string | Required | Opaque token from the Book response in case a challenge is required for 3DS payment. |
options | HandleChallengeOptions | Optional | Configuration for mounting the challenge and receiving optional callbacks. |
HandleChallengeOptions
| Option | Type | Required | Description |
|---|---|---|---|
container | string | HTMLElement | Optional | Use an existing partner-owned element, or its selector, when you want to control where the challenge is mounted. When omitted, Partner SDK creates and manages a default mount element. |
onSuccess | () => void | Optional | Called when the browser challenge completes successfully. |
onFailure | (failure) => void | Optional | Called for every expected failure with reason and optional error. |
HandleChallengeSuccessResult and HandleChallengeFailureResult
| Field | Type | When present | Description |
|---|---|---|---|
success | boolean | Always | true on a resolved result; false on a rejected result. |
reason | HandleChallengeFailureReason | On rejection | Stable failure value for normal control flow. |
error | Error | Optional, on rejection | Optional diagnostic Error. You may use its message and other properties for diagnostics; use reason for control flow. |
HandleChallengeFailureReason
| Reason | Meaning | Partner action |
|---|---|---|
failed | The challenge was not completed successfully. | Ask the backend to check booking status before recovery or retry. |
timeout | The challenge timed out or remained pending. | Ask the backend to check booking status before recovery or retry. |
cancelled | The customer closed the challenge modal. | Ask the backend to check booking status before offering a retry. |
mount_error | The SDK could not mount the challenge. | Treat as a runtime integration error. Ensure that the configured challenge container node exists and is available before calling handleChallenge(). Ask the backend to check booking status if a booking may exist. |
network_error | The challenge module or required network resource could not be loaded. | Show a connectivity error and allow recovery after connectivity is restored. |
context_error | Required payment or challenge context was unavailable. | Show a technical error; do not continue the current challenge without valid context. |
page_changed | The customer navigated away, or the page changed during the challenge. | When the customer returns, restore the booking UI from the partner backend's current state. |
decode_failed | The supplied statusToken could not be processed. | Do not retry the same token; obtain a valid Book-response statusToken. |
Usage examples

Example of 3DS challenge modal displayed by handleChallenge()
Note:This is a dummy 3DS challenge page for illustration only. The actual challenge page and content may depend on the issuer bank. Perform the challenge provided by your Card Network/Bank.
Challenge lifecycle
The SDK displays the 3DS challenge in a browser modal.
- When the customer completes the challenge,
handleChallenge()resolves with{ success: true }. - When the customer closes the modal (using the top-right close control),
handleChallenge()rejects with{ success: false, reason: 'cancelled' }. - All other unsuccessful terminal paths reject with
{ success: false, reason, error? }. erroris optional, untyped diagnostic data. Usereasonfor control flow; do not assume the shape oferroror show it directly to the customer.
Option 1: Promise-result pattern (recommended)
// The partner backend polls booking status independently.
void window.partnerSDK.handleChallenge(statusToken).catch((failure) => {
logChallengeFailure(failure.reason, failure.error);
// Optional: when failure.reason is 'cancelled', your integration may stop
// backend booking-status polling for this attempt. See the Step 8 note.
});
// Process backend booking-status results independently of this Promise.Option 2: Callback-options pattern
Callbacks are optional notifications. Providing a callback does not consume or replace the returned Promise: it still resolves on success and rejects on failure.
// The partner backend polls booking status independently.
void window.partnerSDK.handleChallenge(statusToken, {
container: '#payment-challenge',
onFailure: ({ reason, error }) => {
logChallengeFailure(reason, error);
// Optional: when reason is 'cancelled', your integration may stop
// backend booking-status polling for this attempt. See the Step 8 note.
},
}).catch(() => {
// onFailure already handled the rejected challenge result.
});
Important:A successful
handleChallenge()result confirms only that the customer completed the 3DS/payment challenge. It does not confirm the booking. As described in Step 7, the partner backend must poll booking status and move the customer to the confirmed/thank-you page only after it receivesCONFIRMED.
Step 8 (conditional) — Poll Booking Status API on the backend
- For a challenge-required booking, your backend should poll the Booking Status API concurrently while the browser runs the
handleChallenge()method (see step 7 above) to get the final booking status. Handle the rejected Promise of thehandleChallenge()before processing the Booking Status results. - Use the same opaque
statusTokenreturned in the Book API response for the backend Booking Status polling and the frontendhandleChallenge(). Pass thestatusTokenunchanged; you must not decode, modify, or construct it. - Continue polling with the same
statusTokenwhile the Booking Status API response showsprocessing: true(after the specifiedwaitTimeelapses). - Stop polling when the response shows
processing: false. - Consider the terminal result as either
CONFIRMEDorFAILED.- Move the frontend to the confirmed/thank-you page only after the backend reports
CONFIRMED. - When the backend reports
FAILED, use the normal booking-failure flow.
- Move the frontend to the confirmed/thank-you page only after the backend reports
Note:If
handleChallenge()reportsreason: 'cancelled', you may stop polling the Booking Status API for that attempt. This indicates that the customer closed the challenge; it does not confirm that the booking was cancelled or failed.
Booking Status API request
- API details:
- Method: POST
- URL:
/api/v4/book/status
{
"waitTime": 120,
"tag": "partner unique identifier",
"statusToken": "<status token passed in book 3ds response>"
}| Parameter | Type | Required | Description |
|---|---|---|---|
waitTime | integer | required | Specifies how long (in seconds) to wait for a response. Min: 5, Max: 120 |
tag | string | optional | Partner unique identifier |
statusToken | string | required | The statusToken value returned in the Book API response |
Booking Status API response (same as Book API response)
Refer to the Book API documentation to learn more.
{
"status": "200",
"bookingDetails": [
{
"id": 80001230,
"itineraryID": 70001230,
"processing": false
}
]
}Partner SDK React end-to-end usage example
This example uses the browser CDN global, window.partnerSDK, loaded from Agoda's CDN.
Load and initialize the SDK in the root application layout
This example assumes the partner application has a persistent root AppLayout and an AppContextProvider. Load and initialize the SDK there, rather than in individual routed pages, so session context and the fraud beacon begin across the customer journey.
At a minimum, load and initialize Partner-SDK on the checkout or booking page. For stronger fraud-detection coverage, load and initialize it on every page of the booking journey through checkout, beginning with the home or landing page when possible.
const partnerSdkUrl =
'https://cdn6.agoda.net/s-cdn-partner-sdk/v1/partner.js';
const AppLayout = () => (
<AppContextProvider>
<Header />
<main>
<AppRouter />
</main>
</AppContextProvider>
);
const Header = () => {
const {
markPartnerSdkReady,
recordPartnerSdkWarning,
recordPartnerSdkUnavailable,
} = useAppContext();
const initializePartnerSdk = () => {
// Step 2 — initialize the Partner SDK after the script has loaded.
void window.partnerSDK
.init({
siteId: 'YOUR_AGODA_SITE_ID',
partnerName: 'your-partner-name',
environment: 'sandbox',
onError: (error) => {
// Fraud-beacon errors are non-blocking. Record the warning in
// application context so the partner can monitor or diagnose it.
recordPartnerSdkWarning(error);
},
})
.then(() => {
markPartnerSdkReady();
})
.catch((error) => {
// Invalid SDK configuration prevents initialization.
recordPartnerSdkUnavailable(error);
});
};
return (
<header>
<OtherHeaderComponents />
{/* Step 1 — load partner.js from Agoda's CDN. */}
<script
src={partnerSdkUrl}
onLoad={initializePartnerSdk}
/>
</header>
);
};Keep AppLayout mounted across route changes so the SDK is not repeatedly initialized. The application context can then make SDK readiness available to routed pages, including the booking page.
Submit the booking and handle a required challenge
This example assumes AppLayout has already completed Steps 1 and 2. The booking page does not load or initialize the SDK again; it reads SDK readiness from application context and uses partner-owned booking state and UI components.
The browser generates sdk_context_token in Step 3 and sends it to the partner backend in Step 4. The partner backend performs the Book call in Step 5. This example intentionally does not prescribe the partner backend's Book request or status-polling implementation.
import { useState } from 'react';
export function BookingPage() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
// Partner-owned context, hook, and components used for illustration.
const { isPartnerSdkReady } = useAppContext();
const {
bookingFields,
cardDetails,
isBookingReady,
} = useBookingState();
const handleFinalBookingStatus = (finalStatus) => {
if (finalStatus.bookingStatus === 'CONFIRMED') {
navigateToThankYouPage(finalStatus);
return;
}
setErrorMessage('Your booking could not be confirmed.');
};
const handleBook = async () => {
setIsSubmitting(true);
setErrorMessage(null);
try {
// Step 3 — without partnerName, card details may be omitted.
// With partnerName, all four card fields are required.
let checkoutResult;
try {
checkoutResult = await window.partnerSDK.checkout(cardDetails);
} catch (failure) {
logCheckoutFailure(failure.error);
setErrorMessage('We could not prepare your booking request.');
return;
}
// Step 4 — send sdk_context_token to the partner backend.
//
// Step 5 — the backend calls Affiliate Book API. bookingFields represents
// the partner's existing booking data; it must not contain PAN, CVC,
// expiry, or raw paymentDetails.
const bookingResponse = await submitBookingToPartnerBackend({
...bookingFields,
sdk_context_token: checkoutResult.sdk_context_token,
});
if (
bookingResponse.status === 202 &&
bookingResponse.requiredAction === 'payment_challenge' &&
bookingResponse.statusToken
) {
// Step 6 returned requiredAction as payment_challenge, status as 202 and statusToken from Affiliate Book.
//
// Step 7 — start the browser 3DS/payment challenge with only the
// opaque statusToken returned by Affiliate Book.
//
// Step 8 — concurrently wait for the partner backend's authoritative
// status result. The backend owns polling and its polling identifier.
const finalStatusPromise =
waitForFinalBookingStatusFromPartnerBackend();
void window.partnerSDK.handleChallenge(
bookingResponse.statusToken,
).catch((failure) => {
logChallengeFailure(failure.reason, failure.error);
// Optional: when failure.reason is 'cancelled', your integration
// may stop backend status polling for this attempt. See Step 8.
});
const finalStatus = await finalStatusPromise;
handleFinalBookingStatus(finalStatus);
return;
}
if (!bookingResponse.bookingId) {
setErrorMessage(
'The booking response did not include a booking ID or challenge token.',
);
return;
}
// A non-challenge booking also gets its final result from Step 8.
const finalStatus = await waitForFinalBookingStatusFromPartnerBackend();
handleFinalBookingStatus(finalStatus);
} catch (error) {
// Application or partner-backend failure.
reportUnexpectedBookingFailure(error);
setErrorMessage('We could not complete your booking request.');
} finally {
setIsSubmitting(false);
}
};
return (
<>
<Loader isLoading={isSubmitting} />
<ErrorModal
isOpen={errorMessage !== null}
message={errorMessage}
onClose={() => setErrorMessage(null)}
/>
<BookingForm />
<BookButton
disabled={!isPartnerSdkReady || !isBookingReady || isSubmitting}
isLoading={isSubmitting}
onClick={handleBook}
/>
</>
);
}AppContextProvider, useAppContext, useBookingState, OtherHeaderComponents, AppRouter, BookingForm, Loader, ErrorModal, BookButton, logging functions, and backend functions represent examples of what your application code may look like; adapt as required. bookingFields contains your normal booking-request fields only; it must not include raw card data or paymentDetails.
Updated 14 days ago