How to Integrate
Overview
This scenario walks you through how to integrate Moneris Go devices with your Restaurant Management System (RMS) to support Pay-at-Table. The goal is to allow servers to retrieve a list of their open tables, view table details, accept card payments (including tips) at the table and automatically update the RMS. This improves efficiency, eliminates manual errors, and enhances the guest checkout experience.
By the end, your RMS will be able to communicate with Moneris’ RMS API and Moneris Go terminals to complete an in-person payment flow.
Device Compatibility
Go Pay-at-Table is available on the following Moneris Go terminals:
Note
If you do not have access to any of the above test terminals, click below to order a Moneris Go test terminal and test cards.
Use Case Overview
In many restaurants, servers need to accept payments directly at a guest’s table. This scenario uses Moneris Go Android devices running the Go Pay-at-Table application to make that possible.
The Moneris Go terminal acts as a bridge between the merchant’s RMS and Moneris’ payment infrastructure:
- The RMS connects to Moneris’ RMS cloud API to send and receive table/payment data.
- The Moneris Go Pay at Table runs on the Android terminal and allows customers to complete card transactions at the table.
Note
Every message received or sent from RMS has two-byte header in front of JSON payload, network byte order (big-endian), containing the message length.
See twoBytesHeaderAppender and twoBytesHeaderTrimmer in the full code example section.
The key tasks your integration needs to support:
- Connecting the RMS to Moneris (authenticate)
- Maintain the connection using (echo)
- Retrieving the server’s tables (getTables)
- Getting check info for a selected table (getTable)
- Applying the payment and updating the RMS (applyPayment)
Prerequisites
- A Moneris Go terminal with Pay-at-Table installed
- Your assigned merchantId and apiToken from Moneris
- Terminal ID (terminalId) configured for your device
- RMS server that implements the RMS message structure
- A list of open tables available in your RMS that can be queried by serverId
App Setup
To download and set up the Pay-at-Table app, click here to view the Help Centre article.
1: Authenticate the RMS with Moneris
Purpose:
Before any table or payment data can be exchanged, your RMS must authenticate with Moneris. This happens when a persistent TLS socket connection is opened between your RMS implementation and Moneris’ cloud server. The authenticate request registers your merchant credentials and establishes a secure session. A single connection per MerchantId (MID) is allowed. All subsequent connection attempts will be rejected if there is an active session with the same MID.
Required Inputs:
- merchantId (V13): Your unique 13-digit Moneris merchant number, starts with 0030
- apiToken (V50): Secure credential provided by Moneris
- cloudApiVersion (V20): Typically "1.0"
- requestId (V50): A unique identifier combining terminal ID, timestamp, and sequence number
- requestTimestamp (F19): ISO timestamp of the request (UTC)
Request Example:
{
"action": "authenticate",
"apiToken": "P14i5WS4P0uhgbrnN7BZ",
"cloudApiVersion": "1.0",
"merchantId": "1234567890",
"requestId": "I9000001-20250707-001",
"requestTimestamp": "2025-07-07 10:00:00"
}
Expected Output:
A successful response returns a status of "200" with your merchantId, verifying you are authenticated.
{
"status": "200",
"statusDesc": "OK"
}
authenticate Command
View the full API Specifications for this command
2: Maintain the Connection Using Echo
Purpose:
Once authenticated, your RMS must keep the session alive by sending an echo message every 30 seconds. If no echo is received, Moneris will drop the connection.
Required Inputs:
Use the same authentication headers as in step 1:
- merchantId
- apiToken
- cloudApiVersion
- requestId and requestTimestamp
Request Example:
{
"action": "echo",
"apiToken": "P14i5WS4P0uhgbrnN7BZ",
"cloudApiVersion": "1.0",
"merchantId": "1234567890",
"requestId": "I9000001-20250707-002",
"requestTimestamp": "2025-07-07 10:00:30"
}
Expected Output:
Same structure as the authenticate response—confirming the session is active.
echo Command
View the full API Specifications for this command
3: Retrieve a List of Open Tables
Purpose:
After the server logs into the terminal (via ID entry or card swipe), the Go Pay at Table app sends a getTables request to the RMS to fetch that server’s open tables. These are the tables they’re responsible for, and each table includes the current total and remaining balance.
This API call is used immediately after login or at any point where a server wants to view active tables under their profile. It’s typically triggered when loading the main screen of the Go Pay-at-table interface.
Required Inputs:
- terminalId (V8): Unique ID assigned to the terminal
- merchantId (V13)
- apiVersion (V20): e.g., "1.0"
- requestId, requestTimestamp
- serverId (V50): The ID entered or swiped on the terminal
Request Example:
{
"terminalId": "I9000001",
"merchantId": "1234567890",
"apiVersion": "1.0",
"requestId": "I9000001-20250707-003",
"requestTimestamp": "2025-07-07 10:01:00",
"action": "getTables",
"data": {
"server": {
"serverId": "1234"
}
}
}
Expected Output:
An array of table objects, each with:
- tableId – used for lookup
- tableName – what’s displayed on the terminal (e.g., “Patio-4”, “Main Hall-7”)
- tableTotalAmount – full balance in cents
- tableRemainingAmount – amount left to pay in cents
{
"status": "200",
"data": {
"tables": [
{
"tableId": "T123",
"tableName": "Patio-4",
"tableTotalAmount": 6400,
"tableRemainingAmount": 6400
},
{
"tableId": "T124",
"tableName": "Main Hall-7",
"tableTotalAmount": 8200,
"tableRemainingAmount": 3200
}
]
}
}
getTables Command
View the full API Specifications for this command
4: Retrieve Check Details for a Table
Purpose:
Once a table is selected, the Go Pay at Table sends a getTable request to the RMS to fetch all payment details for that table.
Required Inputs:
- tableId from the previous step
- serverId (optional if swiped)
- terminalId, merchantId, apiVersion, etc.
NOTE: Split by amount and split by check/seat methods will only be supported if enabled through the Go Pay at Table application’s Settings menu.
Request Example:
{
"terminalId": "I9000001",
"merchantId": "1234567890",
"apiVersion": "1.0",
"requestId": "I9000001-20250707-004",
"requestTimestamp": "2025-07-07 10:01:10",
"action": "getTable",
"data": {
"server": {
"serverId": "1234"
},
"tableId": "T123"
}
}
Expected Output:
Includes:
- splitMethod: 1 = full check, 2 = split by amount, 3 = split by check/seat
- masterCheck: Total balance
- Optional checks[]: If the RMS has already split the check by guest
{
"status": "200",
"data": {
"table": {
"tableName": "Patio-4",
"tableTotalAmount": 6400,
"splitMethod": 1,
"masterCheck": {
"checkId": "1",
"checkName": "Full Table",
"totalAmount": 6400,
"remainingAmount": 6400
}
}
}
}
getTable Command
View the full API Specifications for this command
Handling Split Payments
The Pay-at-Table flow supports three payment modes, determined by the splitMethod field returned in the getTable response:
If your RMS supports split payments, follow the instructions below for splitMethod 2 and 3.
| splitMethod | Description |
|---|---|
| 1 | Full check (default) |
| 2 | Split by amount |
| 3 | Split by check/seat (per guest) |
Split-by-Amount (splitMethod = 2) | Split-by-Check/Seat (splitMethod = 3) |
|---|---|
This mode allows multiple guests to pay custom amounts toward a shared check. Go Pay at Table presents a screen for servers or guests to enter the partial payment amount. Steps:
| This mode is used when the table is split into separate checks for each guest/seat (e.g., “Check 1 – John”, “Check 2 – Mary”). Steps:
|
Important:
A check/seat is considered closed when its remainingAmount is 0. The full table closes when all checks/seats are paid.
This additional handling ensures your RMS system supports a full range of real-world dining scenarios—from group bills to separate tabs—while maintaining a consistent integration with the Moneris Go platform
Request Example:
{
"terminalId": "I9000001",
"merchantId": "1234567890",
"apiVersion": "1.0",
"requestId": "I9000001-20250707-004",
"requestTimestamp": "2025-07-07 10:01:10",
"action": "getTable",
"data": {
"server": {
"serverId": "1234"
},
"tableId": "T123"
}
}
Expected Output
{
"terminalId": "I4123456",
"merchantId": "00312312312312",
"configCode": "C12345678SI",
"requestId": "I4123456-2025-08-18-002",
"responseTimestamp": "2025-08-18 10:01:00",
"status": "200",
"statusDesc": "OK",
"data": {
"table": {
"tableId": "01",
"tableName": "Table 1",
"tableTotalAmount": 4200,
"tableRemainingAmount": 4200,
"splitMethod": 3,
"masterCheck": {
"checkId": "1",
"checkName": "Full Table",
"totalAmount": 4200,
"preTaxAmount": 0,
"remainingAmount": 4200,
"receipt": {
"taxes": [],
"discounts": [],
"lineItems": []
}
},
"checks": [
{
"checkId": "1",
"checkName": "Check 1",
"totalAmount": 2000,
"preTaxAmount": 0,
"gratuity": 0,
"remainingAmount": 2000,
"receipt": {
"taxes": [],
"discounts": [],
"lineItems": []
}
},
{
"checkId": "2",
"checkName": "Check 2",
"totalAmount": 2200,
"preTaxAmount": 0,
"remainingAmount": 2200,
"receipt": {
"taxes": [],
"discounts": [],
"lineItems": []
}
}
]
}
}
}
5: Apply a Payment to the POS
Purpose:
After the customer completes a card payment (tip included) using the Moneris Go terminal, the Go Pay-at-Table sends an applyPayment message to finalize the transaction and update the check total.
Required Inputs:
- tableId, checkId: From step 4
- tenderType: "CARD" or "CASH"
- If CARD, you must include:
- cardType (VISA, MC, AMEX, etc.)
- authNumber: Approval code
- referenceNumber: Transaction ID
- lastDigits: Last 4 of the PAN
- paidAmount: Approved amount in cents
- tipAmount: Optional tip in cents
Request Example:
NOTE: paidAmount is inclusive of tipAmount
{
"terminalId": "I9000001",
"merchantId": "1234567890",
"apiVersion": "1.0",
"requestId": "I9000001-20250707-005",
"idempotencyKey": "74ae1696-b1e3-4328-af6d-f1e04d947a13",
"requestTimestamp": "2025-07-07 10:02:00",
"action": "applyPayment",
"data": {
"server": {
"serverId": "1234"
},
"check": {
"tableId": "T123",
"checkId": "1"
},
"payment": {
"tenderType": "CARD",
"card": {
"cardType": "VISA",
"authNumber": "183235",
"referenceNumber": "1020293",
"lastDigits": "1234"
},
"paidAmount": 5400,
"tipAmount": 1000
}
}
}
Expected Output:
The remainingAmount and tableRemainingAmount should decrease accordingly. When both reach 0, the table is considered closed.
{
"status": "200",
"data": {
"table": {
"tableId": "T123",
"tableRemainingAmount": 0
},
"check": {
"checkId": "1",
"remainingAmount": 0
}
}
}
applyPayment Command
View the full API Specifications for this command
Additional Notes:
- All monetary fields (paidAmount, tipAmount, totalAmount) are in cents.
- If tableRemainingAmount > 0 after payment, the same flow can repeat until fully paid.
- Errors (e.g., invalid table ID or missing field) are returned with a status and statusDesc.
6. Full code example
Below is the full code example in JavaScript
import tls from 'tls';
let displayecho = true;
let intervalConnect = null;
let cloud_sock = null;
const ActionType = {
echo: 'echo',
authenticate: 'authenticate',
getTables: 'getTables',
getTable: 'getTable',
applyPayment: 'applyPayment',
};
const errCodesMsgsMap = {
'218': 'Unsupported action Action not supported.',
'600': 'Internal Error', // 600
'601': 'Incorrect Msg Length', // 601
'602': 'JSON Parse Error', // 602
'603': 'Missing Server ID', // 603
'604': 'Invalid Server ID', // 604
'605': 'Invalid Server Tracks', // 605
'606': 'Missing Table ID', // 606
'607': 'Invalid Table ID', // 607
'608': 'Missing Check ID', // 608
'609': 'Invalid Check ID', // 609
'610': 'Missing Payment Obj', // 610
'611': 'Invalid Payment Obj', // 611
'612': 'Missing IdempotencyKey', // 612
'613': 'Invalid split method', // 613
'614': 'Server Not Found', // 614
'615': 'No Table Found', // 615
'616': 'No Check Found' // 616
};
const options = {
autoconnect: true,
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 2000,
reconnectionAttempts: Infinity
};
const cloudSever = {
host: 'patposct.moneris.com',
port: 443
}
const getMID = function () {
return '0030128923365'; //your MID
};
const getCfgCode = function () {
return "C000497ECP";
};
const getApiToken = function () {
return 'C7jZF1ad7IIGCoEIMGoD';
}
let getRequestId = function () {
let now = new Date();
return getMID() + '-' + now.toISOString();
};
let getDateTime = function () {
const now = new Date();
return now.toISOString().replace('T', '-').substring(0, 19);
};
//handle "echo" every 30 seconds
function startEchoInterval(cloud_sock) {
// Make sure we don't start multiple intervals
if (cloud_sock.echoInterval) {
clearInterval(cloud_sock.echoInterval);
}
cloud_sock.echoInterval = setInterval(() => {
if (cloud_sock.gotEchoNoResponse === 0) {
if (!cloud_sock.echoTimeout) {
// set 10 sec for timeout to recevice echo response
cloud_sock.echoTimeout = setTimeout(() => {
console.info('Timeout, closing connection....');
clearInterval(cloud_sock.echoInterval);
clearTimeout(cloud_sock.echoTimeout);
cloud_sock.echoTimeout = null;
cloud_sock.end();
}, 10000);
}
return;
}
// Build echo request
let echoRequest = {
action: 'echo',
apiToken: getApiToken(),
cloudApiVersion: '1.0',
merchantId: getMID(),
requestId: getRequestId(),
requestTimestamp: getDateTime()
};
console.info(
'\x1b[32m',
'Sending Echo Request: ' + new Date().toString() +
'\n' + JSON.stringify(echoRequest, null, 2)
);
cloud_sock.gotEchoNoResponse = 0;
let sendbuf = twoBytesHeaderAppender(echoRequest);
cloud_sock.write(sendbuf); //send echo
}, 30000); // every 30s
}
function stopEchoInterval(cloud_sock) {
if (cloud_sock.echoInterval) {
clearInterval(cloud_sock.echoInterval);
cloud_sock.echoInterval = null;
}
if (cloud_sock.echoTimeout) {
clearTimeout(cloud_sock.echoTimeout);
cloud_sock.echoTimeout = null;
}
}
// build gettables response
function getTables(request) {
let requestId = request.requestId;
let session = request.session;
let requestServerId = request.data.server.serverId;
let requestTrack2;
let tId = request.terminalId;
let mId = request.merchantId;
let response = {};
response = {
'terminalId': tId,
'merchantId': mId,
'configCode': getCfgCode(),
'requestId': requestId,
'responseTimestamp': getDateTime(),
'session': session
}
if (request.data.server.serverId != undefined)
requestServerId = request.data.server.serverId;
if (request.data.server.trackData != undefined)
requestTrack2 = request.data.server.trackData.track2;
if (requestServerId === undefined && requestTrack2 === undefined) {
response.status = "603";
response.statusDesc = errCodesMsgsMap[response.status];
console.info("unable to determine the serverId", request.data.server.serverId);
return response;
}
//for example, your serverId = 1234, and there are 3 tables for this serverId
if (requestServerId == '1234') {
response = {
...response,
'data': {
'tables': [
{
'tableId': '00',
'tableName': '0020',
'tableTotalAmount': 15000,
'tableRemainingAmount': 15000
},
{
'tableId': '01',
'tableName': 'Alphabets',
'tableTotalAmount': 9500,
'tableRemainingAmount': 9500
},
{
'tableId': '02',
'tableName': 'DiningRoom7',
'tableTotalAmount': 24000,
'tableRemainingAmount': 24000
}
]
},
'status': '200',
'statusDesc': 'OK'
}
} else {
response.status = '614';
response.statusDesc = errCodesMsgsMap[response.status];
}
return response;
}
// build gettable response
function getTable(request) {
let requestId = request.requestId;
let session = request.session;
let requestServerId = request.data.server.serverId;
let tableId;
let tId = request.terminalId;
let mId = request.merchantId;
let response = {};
response = {
'terminalId': tId,
'merchantId': mId,
'configCode': getCfgCode(),
'requestId': requestId,
'responseTimestamp': getDateTime(),
'session': session
}
if (request.data.server.serverId != undefined) {
requestServerId = request.data.server.serverId;
} else {
response.status = "603";
response.statusDesc = errCodesMsgsMap[response.status];
console.info("unable to determine the serverId", request.data.server.serverId);
return response;
}
if (request.data.tableId != undefined) {
tableId = request.data.tableId;
} else {
response.status = "606";
response.statusDesc = errCodesMsgsMap[response.status];
return response;
}
//for example, your serverId = 1234, and there are 3 tables for this serverId
if (requestServerId == '1234') {
// in this sample code, response the same detail data for tableId '00' and '01'; it will response error code 615 for tableid '03'.
if ((tableId == '00') || (tableId == '01')) {
response = {
...response,
'data': {
'table': {
'masterCheck': {
'receipt': {
'taxes': [],
'discounts': [],
'lineItems': []
},
'checkId': '1',
'checkName': 'Full Table',
'totalAmount': 15000,
'preTaxAmount': 14000,
'remainingAmount': 15000
},
'checks': [],
'tableId': '00',
'tableName': '0020',
'tableTotalAmount': 15000,
'tableRemainingAmount': 15000,
'splitMethod': 1
}
},
'status': '200',
'statusDesc': 'OK'
}
}
else {
response.status = '615';
response.statusDesc = errCodesMsgsMap[response.status];
}
} else {
response.status = '614';
response.statusDesc = errCodesMsgsMap[response.status];
}
return response;
}
//process payment, update payment information in your database
function processPayment(request) {
let requestId = request.requestId;
let session = request.session;
let requestServerId = request.data.server.serverId;
let tableId, checkId;
let tId = request.terminalId;
let mId = request.merchantId;
let response = {};
response = {
'terminalId': tId,
'merchantId': mId,
'configCode': getCfgCode(),
'requestId': requestId,
'responseTimestamp': getDateTime(),
'session': session
}
if (request.data.server.serverId != undefined) {
requestServerId = request.data.server.serverId;
} else {
response.status = "603";
response.statusDesc = errCodesMsgsMap[response.status];
console.info("unable to determine the serverId", request.data.server.serverId);
return response;
}
if (request.data.check.tableId != undefined) {
tableId = request.data.check.tableId;
} else {
response.status = "606";
response.statusDesc = errCodesMsgsMap[response.status];
return response;
}
if (request.data.check.checkId != undefined) {
checkId = request.data.check.checkId;
} else {
response.status = "608";
response.statusDesc = errCodesMsgsMap[response.status];
return response;
}
if (request.data.payment != undefined) {
//process payment object
} else {
response.status = "611";
response.statusDesc = errCodesMsgsMap[response.status];
return response;
}
// all amount should be your real amount.
response = {
...response,
'status': '200',
'statusDesc': 'OK',
'data': {
'check': {
'checkId': checkId,
'checkName': 'checkName',
'totalAmount': 15000,
'preTaxAmount': 14000,
'remainingAmount': 10000
},
'table': {
'tableId': tableId,
'tableName': '0020',
'tableTotalAmount': 15000,
'tableRemainingAmount': 10000
}
}
}
return response;
}
let connectToRMSCloud = function (cloudSever, options) {
console.info('connectToRMSCloud');
// tls connecion to moneris RMS cloud
cloud_sock = tls.connect(cloudSever, options, function () {
console.info('connected to options: ', cloudSever.host, ' port: ', cloudSever.port);
//authenticate request
let authReq = {
action: 'authenticate',
apiToken: getApiToken(), // your apitoken
cloudApiVersion: '1.0',
merchantId: getMID(),
requestId: getRequestId(),
requestTimestamp: getDateTime()
};
console.info('send to cloud:\n ', JSON.stringify(authReq, null, 2));
let sendbuf = twoBytesHeaderAppender(authReq);
cloud_sock.write(sendbuf);
cloud_sock.on('close', onClose);
cloud_sock.on('data', onData);
cloud_sock.on('error', onError);
cloud_sock.on('timeout', onTimeout);
});
}
let onClose = function () {
//process if connection is closed.
console.info('cloud onClose ', new Date().toString());
stopEchoInterval(cloud_sock);
};
let onData = async function (dataWith2Bytes) {
//
let response = [], sendbuf = [];
let [isErrored, data] = twoBytesHeaderTrimmer(dataWith2Bytes); // only convert to JSON
if (isErrored) {
cloud_sock.end();
return;
}
if (data.action == ActionType.echo) {
console.info('\x1b[32m', ' rev echo:\n' + JSON.stringify(data, null, 2));
} else {
console.info('rev:\n' + JSON.stringify(data, null, 2));
}
let status = parseInt(data.status);
switch (data.action) {
case ActionType.authenticate: //process authenticate action response
let status = parseInt(data.status);
if (status === 200) { // good status
console.info('Authenticate response OK, SetInterval for sending Echo Request');
// set echo interval every 30 sec
startEchoInterval(cloud_sock);
} else {
console.info('\x1b[31m', 'Authenticate response failed: ' + data.status + ' ' + 'Desc: ', data.statusDesc);
cloud_sock.end();
}
break;
case ActionType.echo: //process authenticate action response
if (cloud_sock.echoTimeout != undefined) {
clearTimeout(cloud_sock.echoTimeout);
}
cloud_sock.gotEchoNoResponse = 1;
break;
case ActionType.getTables: //process getTables action request
response = getTables(data);
sendbuf = twoBytesHeaderAppender(response);
cloud_sock.write(sendbuf);
console.info('response on getTables:' + new Date().toString() + '\n' + JSON.stringify(response, null, 2));
break;
case ActionType.getTable://process getTable action request
response = getTable(data);
sendbuf = twoBytesHeaderAppender(response);
cloud_sock.write(sendbuf);
console.info('response on getTables:' + new Date().toString() + '\n' + JSON.stringify(response, null, 2));
break;
case ActionType.applyPayment: //process payment action request
response = processPayment(data);
sendbuf = twoBytesHeaderAppender(response);
cloud_sock.write(sendbuf);
console.info('response on getTables:' + new Date().toString() + '\n' + JSON.stringify(response, null, 2));
break;
default:
response = {
'terminalId': data.terminalId,
'merchantId': data.merchantId,
'configCode': getCfgCode(),
'requestId': data.requestId,
'responseTimestamp': getDateTime(),
'session': data.session,
'status': '218',
'statusDesc': errCodesMsgsMap['218']
}
sendbuf = twoBytesHeaderAppender(response);
cloud_sock.write(sendbuf);
console.info('unkonwn action: \n', JSON.stringify(response, null, 2));
break;
}
};
let onError = function (e) {
console.info('cloud onError', e);
cloud_sock.destroy();
};
let onTimeout = function () {
console.info('onTimeout, end session');
cloud_sock.end();
};
//add 2 bytes of message length at front
const twoBytesHeaderAppender = function (dataObject) {
let bufContent;
bufContent = Buffer.from(JSON.stringify(dataObject), 'utf8');
let bufTwoBytesHeader = Buffer.alloc(2);
bufTwoBytesHeader.writeUInt16BE(bufContent.length, 0);
let buf = Buffer.concat([bufTwoBytesHeader, bufContent]);
return buf;
};
//get message from Moneris cloud
const twoBytesHeaderTrimmer = function (data) {
let isErrored = true;
let message = '';
if (data.length <= 2) {
return [isErrored, message];
}
let dataLen = data.readUInt16BE(0);
if (dataLen !== data.length - 2) {
console.log(dataLen);
console.log(data.toString());
return [isErrored, message];
}
try {
message = JSON.parse(data.slice(2).toString());
isErrored = false;
return [isErrored, message];
} catch (e) {
console.error('JSON parse error', e);
console.error('JSON onParseError', data.toString());
return [isErrored, message];
}
};
/***************************************************************************
* Connect to RMS Cloud *
***************************************************************************/
connectToRMSCloud(cloudSever, options);
/*************************************************************************
* Socket event handler setup *
*************************************************************************/

