PayFacto API - v2.0 - Solution : Secure Hosted Checkout (SHC)

PayFacto API - v2.0 - Solution : Secure Hosted Checkout (SHC)

PayFacto API - v2.0 - Solution : Secure Hosted Checkout (SHC)

Secure Hosted Checkout (SHC) is a JavaScript library you add to your own page. It draws a form inside a secure iframe β€” the cardholder name, card number, expiry, and CVV go directly from the cardholder's browser to PayFacto. Your page never touches raw card data, which keeps you out of scope for the strictest PCI requirements.

How It Works

πŸ”‘

  Your server requests a session token

POST to the SHC session endpoint with your API key. The response returns a one-time token scoped to this checkout.

πŸ“„

  Your server returns the token to the browser

The token is passed back to the browser as JSON. Your API key never leaves the server.

🫜

  The browser calls shc() β€” the iframe appears

The browser passes the token to the global shc() function. The library renders a secure iframe containing the card form inside your #shc-fields div.

πŸ’³

  The cardholder fills in the form and clicks Pay

Card data is submitted directly from the iframe to PayFacto. Your page never sees the raw card data.

βœ…

  PayFacto fires the callback with the result

Your callback function receives a response object. Check response.success first, then verify the HMAC server-side before fulfilling the order.

1

  Prerequisites

πŸ”‘

  API Key

PayFacto provides you with an API key β€” a username and password pair. Base64-encode username:password and put it in the Authorization: Basic ... header on every server-side session request. Never put it in client-side JavaScript or a public repository.

🌐

  HTTPS server

Your server must support HTTPS. All PayFacto endpoints require a secure connection.

πŸ’§

  UAT environment

Use UAT during development: https://uat.ca.shc.payfacto.cloud. Your PayFacto contact will provide the production URL once you are ready to go live.

πŸ“„

  A page you control

SHC works on any HTML page. You add one <script> tag and one <div> β€” SHC handles the rest.

2

  Step 1 β€” Request a Session Token (Server-Side)

⚠️

Call the session endpoint from your server, not the browser. The Authorization header contains your API secret. If you call it from the browser, anyone can steal your key.

ℹ️

The session token is a one-time token that expires in 5 minutes by default (configurable via expires). Request it fresh for each checkout β€” never cache or reuse tokens.

Required session fields

Field Type Description
transactionType string Β· required What to do with the card. VERIFY β€” validate the card, no money moves. PURCHASE β€” charge immediately. PREAUTHORIZATION β€” reserve funds, capture later. PURCHASE and PREAUTHORIZATION also require purchaseAmount.
language string Β· required Language for the form. en = English, fr = French.

Optional session fields

Field Type / Default Description
purchaseAmount integer Amount in cents. $10.25 = 1025. Required for PURCHASE and PREAUTHORIZATION. The amount set here is the amount charged β€” a different value typed in the form is ignored.
amount string How to display the amount field. disabled, editable, or hidden. If purchaseAmount is set and amount is not passed, the field is hidden.
avs string Show address verification fields. postal (postal code only) or both (street + postal). If full AVS is enabled on your account, ignore this option.
disableSubmitIndicator boolean Β· default false Set true to stop the Pay button from changing to a spinner on submit.
email boolean Β· default false Show the email address field. Ignored if 3DS is active β€” when 3DS is on, the email field is shown automatically and is required.
expires string (ISO-8601) When the session token expires. Default: 5 minutes from creation. Format: YYYY-MM-DDTHH:MM:SS.000Z.
ignoreInitAuthTimeout boolean Β· default false Set true to ignore a 3DS device-collection timeout and continue the 3DS flow. Warning: this may shift fraud liability to the merchant.
invoice string How to display the invoice field. disabled, editable, or hidden.
invoiceNumber string Β· max 25 chars Pre-fill the invoice number. If invoice is not set, the field displays as read-only.
transactionDescription string Text shown in the description field of the generated receipt.
useRecaptcha boolean Β· default true Set false to disable Google reCAPTCHA. Only disable if reCAPTCHA causes issues on your site β€” disabling it increases exposure to automated attacks.
webhookUrl string (URL) An extra HTTPS URL that PayFacto will POST the transaction result to in addition to the browser callback.
JSON β€” Session request with all options
{
"transactionType": "PURCHASE",
"purchaseAmount": 1025,
"language": "en",
"amount": "hidden",
"avs": "both",
"email": true,
"invoice": "editable",
"invoiceNumber": "INV-001",
"transactionDescription": "Order #2024-001",
"expires": "2026-06-30T23:59:59.0Z",
"useRecaptcha": true,
"webhookUrl": "https://your-site.com/webhook",
"disableSubmitIndicator": false,
"ignoreInitAuthTimeout": false
}
JavaScript β€” Server-side session request
// Server-side β€” Node.js example
const response = await fetch(
'https://uat.ca.shc.payfacto.cloud/secure-hosted-checkout/v1/sessions',
{
method: 'POST',
headers: {
'Authorization': `Basic ${Buffer.from('username:password').toString('base64')}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
transactionType: 'PURCHASE',
purchaseAmount: 1025, // $10.25 in cents β€” mandatory for PURCHASE
language: 'en'
})
}
);

const { token } = await response.json();
// Return this token to the browser β€” never expose your API key
β˜…

  Webhook Best Practices

Webhooks are a window into your system. Care must be taken that third parties are unable to send false or misleading information. The following tips can greatly reduce the possibility of bad actors introducing false information into your system.

🎲

  Randomize the Endpoints

When sending an endpoint to SHC, add a random element to it (i.e. /webhook/{randomId}). Map the random element (randomId) to the secureId in the token. When a message is received, compare the randomId and the secureId received to the stored pair to ensure they match, otherwise reject the message.

πŸ”’

  Always Verify the HMAC

When the message is received, verify the HMAC as shown in the Verifying the HMAC section to ensure the message has not been tampered with.

πŸ””

  Treat the Message as a Notification

The message sent to your webhook is a notification, not a source of truth. Reconcile your transactions with data provided securely by PayFacto.

⚑

  Acknowledge Quickly

Send an acknowledgement as soon as you receive a notification β€” this will greatly reduce the probability of getting multiple notifications for the same transaction. Ensure that a 2XX response, such as a 200, is sent to avoid confusion.

3

  Step 2 β€” Add the Script and Placeholder to Your Page

ℹ️

Add the SHC library script tag to your page <head> and an empty <div> wherever you want the card form to appear. The default container id is shc-fields β€” you can use a different id but must tell SHC about it via the sourceElement option.

HTML β€” Script tag and placeholder div
<!-- 1. Add the SHC script to your page <head> -->
<script src="https://uat.ca.shc.payfacto.cloud/secure-hosted-checkout/v1/browser/shc.js"></script>

<!-- 2. Add a placeholder div where the iframe will appear -->
<div id="shc-fields"></div>
4

  Step 3 β€” Pass the Token to shc() in the Browser

ℹ️

Your server returns the session token to the browser as JSON. The browser then calls the global shc() function with the token as the first argument. The second argument is a callback. The callback fires when the cardholder clicks Pay. The third argument is optional and is an object containing browser-side options.

JavaScript β€” Fetch token from server and call shc()
// Fetch the session token from your server, then call shc()
function getShcToken(done) {
fetch('/your-server/get-shc-token')
.then(r => r.json())
.then(data => done(data.token));
}

getShcToken(shcToken => {
shc(shcToken, response => handleResponse(response));
});

// shc() draws the iframe inside #shc-fields automatically

Third argument β€” browser-side options

ℹ️

These options are different from session options β€” they control how the iframe behaves once it is created. Pass them as the third argument to shc(). Each entry in fields must have both visibility and value or SHC throws an error.

Option Description
sourceElement Override the default shc-fields container. Pass the id of any div on your page.
cardholderName Pre-fill the cardholder name and hide the name field.
fields Object of field overrides. Each key is a field name; each value must have visibility (hidden, disabled, or enabled) and value.
JavaScript β€” Third argument (browser options)
const options = {
sourceElement: 'my-checkout-div', // use a different container id
cardholderName: 'Jane Doe', // pre-fill and hide the name field
purchaseAmount: 1025, // hidden field, required for 3DS
fields: {
email: { visibility: 'enabled', value: 'jane@example.com' },
postal: { visibility: 'disabled', value: 'M5V 1A1' },
address: { visibility: 'hidden', value: '123 Main St' }
}
};

shc(shcToken, response => handle(response), options);
5

  Step 4 β€” Handle the Callback Response

⚠️

Never fulfil an order based on the callback alone. Always verify the HMAC on your server (Step 5) before releasing goods or services.

Successful response (Pay by card)

JSON β€” Success callback
{
"success": true,
"response": {
"authCode": "754062",
"avs": "U",
"brand": "VISA",
"card": "************4242",
"cvv2Cvc2Status": "M",
"expiry": "1226",
"invoiceNumber": "41343428",
"isoResponseCode": "00",
"issuerMessage": "AP",
"lastFour": "4242",
"name": "Test Card",
"paymentMethod": "VISA CARD",
"purchaseAmount": 500,
"purchaseCurrency": "CAD",
"purchaseDate": "2026-06-23 17:49:16 EDT",
"secureId": "33f19cdc-5b74-456a-8e95-51527c5cc910",
"status": "SUCCEEDED",
"store": {
"address": {
"line1": "123 Some Street",
"city": "Toronto",
"state": "ON",
"postalCode": "A1A 1A1",
"country": "CA"
},
"email": "contact@merchant-store.com",
"name": "Merchant Store",
"phone": "(123) 456-7890",
"website": "www.merchant.com"
},
"token": "c2FLxMZn51DZAMjAM3CkcppWf",
"transactionDescription": "Transaction Description",
"transactionId": "af569819-0125-4979-9dff-a81cda21ebd0",
"transactionType": "PURCHASE",
"type": "CREDIT",
},
"metadata": {
"algorithm": "sha256",
"HMAC": "3928cbccac77e3b66404c37bbfa2ec44dc8f839ed478fcc4637e29f600ec70e4",
"receipt": "",
"timestamp": "2026-06-23 17:49:16 EDT"
}
}

Successful response (Pay by konek)

JSON β€” Success callback
{
"success": true,
"response": {
"accountType": "BANK_ACCOUNT_CHEQUING",
"expiry": "1230",
"invoiceNumber": "61341840",
"lastFour": "6457",
"paymentConsentId": "37c3449c-e876-4190-9284-3a744048e8b5",
"paymentMethod": "INTERAC_DIRECT",
"purchaseAmount": 500,
"purchaseCurrency": "CAD",
"purchaseDate": "2026-06-23 17:34:12 EDT",
"secureId": "d2f01954-a644-45b9-b1f3-03930f436af5",
"status": "SUCCEEDED",
"store": {
"address": {
"line1": "123 Some Street",
"city": "Toronto",
"state": "ON",
"postalCode": "A1A 1A1",
"country": "CA"
},
"email": "contact@merchant-store.com",
"name": "Merchant Store",
"phone": "(123) 456-7890",
"website": "www.merchant.com"
},
"transactionDescription": "Transaction Description",
"transactionId": "3ab9cd17-babb-4c53-aa02-23ed4bb0e962",
"transactionType": "PURCHASE",
"type": "INTERAC_DIRECT"
},
"metadata": {
"algorithm": "sha256",
"HMAC": "ee0600b30d0f0c955d40854334fd97245eb3c30d4bebc60668310c83fb221c4c",
"receipt": "",
"timestamp": "2026-06-23 17:34:12 EDT"
}
}
Field Description
success true if the transaction succeeded.
response.authCode Authorization code from the card issuer. Display on the receipt.
response.status SUCCEEDED, DECLINED, etc.
response.brand Card brand β€” Visa, Mastercard, Amex, etc.
response.card Masked card number, safe to show on a receipt.
response.lastFour Last 4 digits of the card.
response.token Reusable card token. Store securely for future charges (see Part 4 β€” Using Saved Tokens).
response.transactionId Unique PayFacto transaction identifier.
response.receipt HTML receipt you can display on screen or send by email.
response.purchaseAmount Amount charged, in cents.
response.purchaseCurrency Currency of the transaction.
metadata.HMAC SHA-256 fingerprint of the response. Verify this on your server before fulfilling the order.
metadata.algorithm Hashing algorithm used for the HMAC. Currently always sha256.
metadata.timestamp ISO-8601 timestamp of the response.

Additional response fields

Field Description
accountType Pay-by-bank bank account type.
avs The AVS response code. Omitted for Konek.
cvv2Cvc2Status The CVV Valid values are 'M' for PASS and 'N' for FAIL. Omitted for Konek.
email The cardholder's e-mail address.
expiry Expiry date of the card.
invoiceNumber Passed invoice number. If no invoice number is passed then one is generated on the fly.
isoResponseCode ISO response code from issuer. Omitted for pay-by-bank transactions.
issuerMessage Message from the issuer. Omitted for pay-by-bank transactions.
name Cardholder name. Omitted for Konek.
paymentConsentId Verification ID for pay-by-bank.
paymentMethod Card brand and type. If the transaction is pay-by-bank then the type is INTERAC_DIRECT.
processorAvsResultCode The AVS response code returned from the processor. Value varies by card brand and processor. Omitted for Konek.
purchaseDate The date and time stamp when the transaction was processed.
secureId The session ID.
store.address.line1 Store address line1.
store.address.city Store city.
store.address.state Store state, province, or territory.
store.address.postalCode Store postal or ZIP code.
store.address.country Store ISO 3166-1 alpha-2 country code. [CONFIRM: source list named this field "store.address.store" β€” reproduced here as "country" to match the field name shown in the JSON example]
store.email Store support contact e-mail address.
store.name Name of the store the merchant account is associated with.
store.phone Store support contact phone number.
store.website Store website.
transactionDescription The value that shows up on the description field of a receipt.
transactionType The type of requested transaction. Valid values are 'PREAUTHORIZATION', PURCHASE, and 'VERIFY'.
type Card type. Valid values are 'CREDIT' and 'DEBIT'.
algorithm The algorithm used to generate an HMAC.
HMAC The contents of the response converted to a string and then encrypted using the sha256 algorithm and the merchant account's private API password.
timestamp The time the transaction was generated.
receipt An HTML receipt that can be displayed on the screen or embedded in an e-mail.

Error response

JSON β€” Error callback
{
"success": false,
"response": {
"type": "CARD_DECLINED",
"code": "INVALID_NUMBER",
"message": "card number is incorrect"
}
}
Error type Error codes
CARD_DECLINED INVALID_CARD_NUMBER, AVS_DECLINED, CARD_BRAND_NOT_SUPPORTED, CVV_DECLINED, RISK_DECLINED, ISSUER_DECLINED
AMOUNT_DECLINED AMOUNT_TOO_LARGE, AMOUNT_TOO_SMALL
UNAUTHORIZED β€”
BATCH_CLOSING BATCH_CLOSING
RECAPTCHA_VERIFICATION_FAILED β€”
TOO_MANY_REQUESTS β€”
INTERNAL_ERROR β€” or ISSUER_TIMEOUT
TIMEOUT TIMEOUT

3D Secure error codes

Type Code Meaning
CARD_ERROR NOT_VERIFIED Could not verify the card number.
CARD_ERROR NOT_AUTHENTICATED The issuer rejected the card during 3DS.
DOWNSTREAM_ERROR NOT_VERIFIED The 3DS provider is having technical issues.
6

  Step 5 β€” Verify the HMAC (Server-Side)

⚠️

Always verify HMACs on your server, never in the browser. The check only proves anything if your API secret stays secret.

ℹ️

Run JSON.stringify(response.response) β€” the inner response object, not the full envelope β€” through HMAC-SHA256 keyed with your API secret. Compare the result against metadata.HMAC. If they differ, discard the response and do not fulfil the order.

JavaScript β€” HMAC verification
const crypto = require('crypto');

function verifyHmac(shcResponse, apiSecret) {
const computed = crypto
.createHmac(shcResponse.metadata.algorithm, apiSecret)
.update(JSON.stringify(shcResponse.response))
.digest('hex');

return computed === shcResponse.metadata.HMAC;
}

if (!verifyHmac(response, process.env.API_SECRET)) {
throw new Error('HMAC mismatch β€” do not process this response');
}
// Safe to fulfil the order
7

  Custom Forms

ℹ️

If the default styling options are not enough, you can build your own form. Wrap it in a <div> with an id, and pass that id to shc() as sourceElement. Give any input the matching SHC field id to activate validation, formatting, and secure submission for that field.

⚠️

Before SHC copies your custom form into the secure iframe, it strips <script>, <iframe>, <object>, <a> tags, any <input type=submit> or <button type=submit>, the action and method attributes from <form> tags, and all inline JavaScript event handlers. This prevents code injection.

HTML + JavaScript β€” Minimal custom form
<div id="custom-form">
<form style="display: flex; flex-direction: column; gap: 12px;">
<!-- Hidden name field β€” value pre-filled -->
<input type="hidden" name="name" value="JOHN DOE" />

<!-- SHC handles validation for fields with matching ids -->
<input id="shc-card-input" name="card" type="text" placeholder="1234 1234 1234 1234" required autocomplete="cc-number" />
<input id="shc-expiry-input" name="expiry" type="text" placeholder="MM / YY" required />
<input id="shc-cvv-input" name="cvv" type="text" placeholder="123" required autocomplete="cc-csc" />

<!-- 3DS hidden fields (required if 3DS is enabled on your account) -->
<input id="purchaseAmount" name="purchaseAmount" type="hidden" value="1025" />
<input id="purchaseCurrency" name="purchaseCurrency" type="hidden" value="CAD" />
<input id="purchaseDate" name="purchaseDate" type="hidden" value="2026-06-01T14:30:00.000Z" />

<button id="shc-pay-button" type="button">Pay</button>
</form>
</div>

<script>
shc(shcToken, response => handle(response), { sourceElement: 'custom-form' });
</script>

3DS hidden fields β€” required if your account uses 3D Secure

Hidden field id Value format Description
purchaseAmount Integer (minor units) Amount in cents. $9.20 = 920.
purchaseCurrency String Must match your merchant account currency. Usually CAD.
purchaseDate ISO-8601 extended Date/time in YYYY-MM-DDTHH:MM:SS.000Z format.

Sending messages from custom buttons

Add a data-message attribute to any button inside your custom form. When clicked, SHC sends the message to the parent page. Listen with a message event listener:

JavaScript β€” Custom button messages
window.addEventListener('message', event => {
console.log(event.data); // { message: 'whatever you put in data-message' }
});
8

  Styling the Form

ℹ️

SHC reads a <style> tag with the id shc-style and injects it into the iframe. This is the only way to style the iframe β€” normal CSS on your page does not reach inside it.

HTML β€” Style block location
<style id="shc-style">
/* Your custom styles here β€” applied inside the SHC iframe */
</style>

Common style recipes

CSS β€” Hiding icons, removing borders, showing card brands
/* Hide all field icons */
.confidante-field-img svg { visibility: collapse; }

/* Hide just the card icon */
#shc-card-img { visibility: collapse; }

/* Move icons to the right side */
.confidante-field-img svg { left: auto; right: 5px; }
.confidante-field-validate svg { display: none; }

/* Show supported card brands (hidden by default) */
#shc-card-brands { display: flex; justify-content: center; }

/* Show the session timeout countdown (hidden by default) */
#shc-countdown { display: block; }

/* Remove the outer border */
#target { border: 0; padding: 0; padding-top: 20px; }
CSS β€” Full re-skin (underline style)
/* Full re-skin β€” underline style with a custom button color */
#shc label {
margin-bottom: 15px;
position: relative;
border: 0;
border-bottom: 1px solid #ddd;
box-shadow: none;
}
#shc input:not(.shc-hidden-fields) {
width: 100%;
padding: 20px 0 10px 0;
margin-top: 20px;
border: none;
outline: none;
}
#shc span.confidante-field-span {
position: absolute; top: 0; left: 0;
transform: translateY(40px);
font-size: 0.825em;
transition-duration: 400ms;
}
#shc button {
padding: 15px 0;
margin-top: 20px;
background: #BC955C;
color: #fff;
border: 1px solid #BC955C;
cursor: pointer;
border-radius: 3px;
}
#shc label:focus-within > span.confidante-field-span,
#shc input:not(:placeholder-shown) + span.confidante-field-span {
color: #BC955C;
transform: translate(32px, 12px);
}

Available CSS ids and classes

Selector What it targets
.confidante-field-label Each field label wrapper
.confidante-field-img Icon area at the start of each field
.confidante-field-input The input element inside each field
.confidante-field-span The label text (animates on focus)
.confidante-field-validate Validation icon area at the end of each field
.confidante-field-mask Card number masking overlay
#shc-{name}-input Target a specific field, e.g. #shc-card-input
#shc-pay-button The Pay button
#shc-card-brands Card brand logos row (hidden by default)
#shc-countdown Session timeout countdown (hidden by default)
#target The outer iframe container / border
β˜…

  Listening for Events and Sending Commands β€” shcMessenger

ℹ️

shc() returns an shcMessenger object. Use .on() to listen for events from the form, and the send methods to control the form programmatically.

JavaScript β€” shcMessenger events and commands
let shcMessenger;

getShcToken(shcToken => {
shcMessenger = shc(shcToken, response => handle(response));
});

// ── Listen for form events ───────────────────────────────────
shcMessenger.on('shc-card-brand', data => console.log('Brand:', data.brand));
shcMessenger.on('shc-ready-to-submit', data => console.log('Ready:', data.isReady));
shcMessenger.on('shc-submit', () => console.log('Form submitted'));
shcMessenger.on('shc-3ds', () => console.log('3DS started'));
shcMessenger.on('shc-3ds-challenge', () => console.log('3DS challenge shown'));
shcMessenger.on('shc-response', data => console.log('Result:', data));
shcMessenger.on('shc-reset', () => console.log('Form reset'));

// ── Send commands to the form ────────────────────────────────
shcMessenger.sendBlockSubmit(); // disable Pay button programmatically
shcMessenger.sendUnblockSubmit(); // re-enable Pay button
shcMessenger.sendSubmit(); // submit the form (ignored if fields invalid)
shcMessenger.sendCancel(); // destroy the form entirely
Event When it fires Payload
shc-card-brand As soon as the card brand is detected from the number entered. { brand: "Visa" }
shc-ready-to-submit All fields are valid and the Pay button is enabled β€” or re-fires with isReady: false if a field becomes invalid again. { isReady: true }
shc-submit The cardholder pressed Pay and the form was submitted. β€”
shc-3ds The 3DS process has started. β€”
shc-3ds-challenge The 3DS challenge step is being shown to the cardholder. β€”
shc-response The same data as the main callback. Useful if you need to react in more than one place. response object
shc-reset A recoverable error occurred (often a digital wallet issue) and the form has reset itself. β€”
β˜…

  Helper Endpoints

ℹ️

If you need to retrieve the result of a session after the callback fires β€” for example, to reconcile server-side β€” use these GET endpoints. Plug the secureId from the response into the URL.

Endpoints β€” Session result lookup
// All base URLs: https://uat.ca.shc.payfacto.cloud/secure-hosted-checkout/v1

GET /sessions/{secureId}/response // Full response from a session
GET /sessions/{secureId}/transaction // Underlying transaction data
GET /sessions/{secureId}/receipt // HTML receipt (if generated)
Endpoint What it returns
GET /sessions/{secureId}/response The full response object from the session.
GET /sessions/{secureId}/transaction The underlying transaction data.
GET /sessions/{secureId}/receipt The HTML receipt, if one was generated.
β˜…

  3D Secure Test Cards (UAT)

ℹ️

Use these card numbers in UAT to test every 3DS outcome. For all scenarios: Cardholder Name = Test Card, CVV = 123 (or 1234 for Amex).

Scenario Visa Mastercard Amex Expiry
Frictionless β€” no 3DS 4100000000000100 5100000000000107 340000000000108 08/25
Frictionless β€” with 3DS 4100000000600008 5100000000600005 340000000600006 08/26
3DS challenge (password: 123456) 4100000000005000 5100000000005007 340000000005008 08/26
Failed 3DS (password: 111111) 4100000000300005 5100000000300002 340000000300003 08/26
Authentication unavailable 4100000000400003 5100000000400000 340000000400001 08/26
Authentication rejected 4100000000500000 5100000000500007 340000000500008 08/26
⚠️

ignoreInitAuthTimeout β€” Before 3DS starts, SHC collects device information. This can occasionally time out, failing the transaction. Setting ignoreInitAuthTimeout: true in the session request tells SHC to continue anyway. Use only if your failure rate from this timeout is high β€” bypassing it may shift fraud liability to you.