We are officially abandoning the North iFrame JavaScript SDK approach for this application.
Do not continue any work on:
-
pay-now.min.js
-
hosted fields
-
browser-side tokenization
-
SDK certification pages
-
PayNow, PayNowSdk, getToken(), or any iframe SDK integration attempts
We are implementing North using Custom Pay API instead.
This is the correct path for us because:
-
GiveHub 1.0 has successfully used North Custom Pay API for many years
-
the iFrame SDK effort has been inconsistent, undocumented, and not a productive use of time
-
we already have strong evidence for the correct server-side API pattern
Goal
Build a backend-driven North payment integration using Custom Pay API /sale.
Architecture
Frontend
Build a secure payment form that collects:
-
card number
-
expiration date
-
CVV
-
billing address
-
ZIP
-
first name
-
last name
-
amount
The browser should submit payment data over HTTPS to our backend only.
Backend
Build:
POST /api/north/charge
This endpoint should:
-
validate incoming payment data
-
load the correct North config for the organization
-
construct the North /sale JSON payload
-
compute epi-signature
-
send the request server-to-server to North
-
normalize the response back to frontend
Confirmed North API details
Base URL
From our existing generated API client:
-
base URL defaults to:
https://epi.epxuap.com
Sale endpoint
Use:
-
POST /sale
This is confirmed in our existing salesApi.ts implementation.
Required headers
Set:
-
Content-Type: application/json
-
EPI-Id: <merchant four-part processing key>
-
EPI-Signature: <HMAC signature>
-
optional EPI-Trace: <merchant trace value>
-
optional EPI-Version: 1.0
Signature generation
North confirms:
-
signature is HMAC-SHA256
-
signed data is the concatenation of:
-
endpoint path
-
exact raw JSON payload string
-
So implement:
epi-signature = HMAC_SHA256(epiKey, "/sale" + JSON.stringify(payload))
Important:
-
sign the endpoint path "/sale" — not the full URL
-
use the exact JSON string sent in the request body
-
do not pretty-print or reformat after signing
-
use lowercase hex output unless North explicitly requires uppercase
Response handling
North responses use:
-
reference
-
data
-
errors
Approvals should be identified by:
-
data.response === "00"
Anything else is decline/failure.
Example approval fields:
-
data.authorization
-
data.response
-
data.text
-
reference.bric
Existing confirmed North client behavior from our reference code
Our existing salesApi.ts confirms:
-
/sale is the auth+capture endpoint
-
headers must include:
-
EPI-Id
-
EPI-Signature
-
Content-Type
-
EPI-Trace optional
-
-
request body is JSON
-
success is any 2xx HTTP response, but application-level approval still needs to be checked using North’s response code
Please model the new integration after that server-side pattern.
Per-org configuration to store
At minimum, store per organization:
-
epiId
-
epiKey
-
environment
-
baseUrl if environment-specific override is needed
-
any batch/default transaction settings required by North
-
optional trace prefix or terminal config if required
Do not hardcode merchant credentials in frontend code.
Security rules
This is a direct server-side card flow.
Requirements:
-
do not log raw card number
-
do not log CVV
-
do not store raw card data
-
do not persist PAN anywhere
-
do not include PAN/CVV in structured logs, traces, error payloads, or analytics
-
only pass card data through to North for the live transaction request
Allowed logs:
-
org id
-
environment
-
amount
-
last4 only if needed
-
North response code
-
transaction id / BRIC
-
approval/decline result
Frontend request shape
The frontend should POST JSON to our backend like:
{ "orgId": 1, "amount": 25.00, "firstName": "John", "lastName": "Doe", "cardNumber": "4111111111111111", "expirationDate": "1227", "cvv": "123", "address": "123 Main St", "zip": "30062", "email": "john@example.com", "description": "Donation" }
ZIP is required.
Billing address should also be collected and sent if North AVS supports/uses it.
Backend endpoint contract
Build:
POST /api/north/charge
Pseudo-flow:
-
Validate required fields:
-
orgId
-
amount
-
cardNumber
-
expirationDate
-
cvv
-
zip
-
address if AVS requires it
-
-
Load org-specific North config:
-
epiId
-
epiKey
-
environment/baseUrl
-
-
Build /sale payload using North field names
-
Serialize payload exactly once:
-
const rawJson = JSON.stringify(payload)
-
-
Compute signature:
-
HMAC_SHA256(epiKey, "/sale" + rawJson)
-
-
Send:
-
headers:
-
Content-Type: application/json
-
EPI-Id
-
EPI-Signature
-
optional EPI-Trace
-
-
Parse response:
-
if data.response === "00" => success
-
otherwise => decline/failure
-
-
Return normalized response to frontend
Required implementation files
1) Signature helper
Create a helper similar to:
import crypto from "crypto"; export function generateEpiSignature( endpoint: string, payload: unknown, epiKey: string ): string { const rawJson = JSON.stringify(payload); const toSign = endpoint + rawJson; return crypto.createHmac("sha256", epiKey).update(toSign).digest("hex"); }
2) North charge backend
Build a backend endpoint that:
-
constructs the sale payload
-
computes signature
-
posts to North
-
normalizes the result
Example structure:
const endpoint = "/sale"; const payload = { account: cardNumber, amount, expirationDate, cvv2: cvv, // include correct North transaction fields here // include AVS fields here }; const signature = generateEpiSignature(endpoint, payload, epiKey); const response = await fetch(baseUrl + endpoint, { method: "POST", headers: { "Content-Type": "application/json", "EPI-Id": epiId, "EPI-Signature": signature, "EPI-Trace": `gh-${Date.now()}` }, body: JSON.stringify(payload) });
3) Frontend
Build a simple card form that posts to our backend only.
Do not call North directly from the browser.
Important payload guidance
From North’s examples and notes we know valid field names can include things like:
-
account
-
amount
-
transaction
-
taxAmount
-
taxExempt
Our implementation must use the correct /sale payload shape for ecommerce card-not-present transactions.
Do not guess field names like:
-
industry type
-
card entry method
-
batch id
-
AVS object structure
Use the Custom Pay API spec / legacy reference pattern and confirm the final payload shape before coding.
If needed, mirror the naming used in GiveHub 1.0.
Approval / decline normalization
Return this shape on success:
{ "success": true, "transactionId": "...", "authCode": "...", "message": "Approved" }
Return this shape on failure:
{ "success": false, "error": "...", "message": "Declined or failed" }
Use:
-
data.response === "00" for approval
-
data.authorization for auth code if present
-
reference.bric for transaction reference if present
What not to do
Do not:
-
continue any iframe SDK work
-
use pay-now.min.js
-
build hosted fields
-
use PayNow / PayNowSdk
-
invent new North integration patterns
-
guess signature rules
-
sign the full URL
-
log raw payment data
What I need from you before coding
Reply with a short technical plan that includes:
-
exact backend endpoint(s) you will create
-
exact frontend request payload shape
-
exact North /sale payload shape you will send
-
exact TypeScript code you will use to generate EPI-Signature
-
exact sandbox and production base URLs
-
exact per-org config fields you will store
-
exact AVS / ZIP validation behavior
-
exact logging redaction rules
Do not start coding until you provide this implementation plan.
This should be implemented as a straightforward backend Custom Pay API flow, modeled after our proven North server-side pattern, not as a new browser-SDK integration.
