PamiPOP device can be discovered on your local network by sending a UDP broadcast message to the port 7263.
The message should be Who has <ENDPOINT_ID> where <ENDPOINT_ID> is the unique identifier of your PamiPOP device. The device will respond with I have <ENDPOINT_ID> and you can then use the IP address of the responding device to create a connection for API calls.
An example of the discovery process implemented in C# (.NET) is shown below:
const int port = 7263; const string endpointId = "NY_009_OS_PRAG"; async Task Main(string[] args) { var tokens = new CancellationTokenSource(); var udpClient = new UdpClient(port) { EnableBroadcast = true }; Listen(udpClient, tokens.Token); await Send(udpClient); await Task.Delay(10000); //wait 10 seconds tokens.Cancel(); udpClient.Close(); } async Task Send(UdpClient client) { var message = $"Who has {endpointId}"; var messageBytes = Encoding.ASCII.GetBytes(message); var broadcastEp = new IPEndPoint(IPAddress.Broadcast, port); await client.SendAsync(messageBytes, messageBytes.Length, broadcastEp); } void Listen(UdpClient client, CancellationToken cancellationToken) { Task.Run(async () => { while (!cancellationToken.IsCancellationRequested) { var received = await client.ReceiveAsync(cancellationToken); var message = Encoding.ASCII.GetString(received.Buffer); Console.WriteLine($"{received.RemoteEndPoint.Address} sent '{message}'"); } }, cancellationToken); }
The output from the code above will look like this:
192.168.1.54 sent 'I have NY_009_OS_PRAG'
The IP address shown in the output is the address of the PamiPOP device on your network. You can use this address to connect via WebSocket as described in the Connecting section below.
To authenticate to the PamiPOP, you will need a valid API key. This key is used to identify your system and ensure secure communication with the PamiPOP device.
Two headers are required for every WebSocket connection:
X-API-Key should contain your API key, which you can obtain directly from PamiTV. This key is unique to your system and should be kept private.
X-API-Client should contain a string that identifies your system, such as the name of your application or service. This must be the same name you used to register for an API key.
To interact with the API, establish a WebSocket connection to the following endpoint:
ws://<host>:7264/commands
Replace <host> with your PamiPOP's address found during the discovery process. See the Device Discovery section for more details.
Example (using C#):
var ipAddress = "192.168.1.54"; var webSocket = new ClientWebSocket(); webSocket.Options.SetRequestHeader("X-API-Key", "your_key_here\keep\private=="); webSocket.Options.SetRequestHeader("X-API-Client", "Your System's Name Goes Here"); await webSocket.ConnectAsync(new Uri($"ws://{ipAddress}:7264/commands"), CancellationToken.None);
All communication is performed using structured messages. The primary message types are Command (sent by the client -- your system) and Result (sent by the PamiPOP device). Messages can be serialized as either JSON or XML.
When you connect your WebSocket, you can specify the desired format by setting the Content-Type header. If the Content-Type header is restricted in your programming framework (like .NET), you can instead set the X-Mime-Type header to select your message format.
Valid values for Content-Type or X-Mime-Type are:
application/json for JSON formatapplication/xml for XML formatIf no header is provided, the default message format is JSON.
Notes:
{
"Name": "Prompt",
"FlowId": "2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"DisplayText": "$10.23 - Approve Amount?",
"FooterText": "",
"Buttons": [
{
"Text": "Approve",
"Value": "approve"
},
{
"Text": "Reject",
"Value": "reject",
"AccentColor": "Red"
}
],
"Data": null
}
<?xml version="1.0" encoding="utf-8"?> <Command> <Name>Prompt</Name> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <DisplayText>$10.23 - Approve Amount?</DisplayText> <FooterText /> <Buttons> <Button> <Text>Approve</Text> <Value>approve</Value> </Button> <Button> <Text>Reject</Text> <Value>reject</Value> <AccentColor>Red</AccentColor> </Button> </Buttons> <Data /> </Command>
Fields:
Name (string): The command to execute - see Valid Command Names.FlowId (uuid): The unique identifier of the currently active flow - see Example Flows.DisplayText (string): The title text to display at the top of the payment area. The text will word-wrap around 24 characters on most HD screens. Two (2) lines of text can be displayed for most commands.FooterText (string): The text to display at the bottom of the payment area. Only commands with no other content in the payment area are allowed to use footer text. Note that FooterText will not be shown if DisplayText is blank / null. DisplayText is required for FooterText to be displayed. Carriage returns are allowed in this field (\n in JSON and 
 in XML); otherwise the text will word-wrap around 30 characters on most HD screens.Buttons (array): An array of button objects to display to the user. Only valid on Prompt commands.
Text (string): The text displayed on the button.Value (string): The value returned when the button is clicked.AccentColor (optional, string): The accent color of the button (shown as a small border around the button), e.g., "Red", "Green". Common named colors are supported.Data (string): Additional data used to provide context for the command.
{
"FlowId": "2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"CommandName": "Prompt",
"SelectedButton": {
"Text": "Approve",
"Value": "approve"
},
"Value": null,
"Data": null,
"Error": null
}
<?xml version="1.0" encoding="utf-8"?> <Result> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <CommandName>Prompt</CommandName> <SelectedButton> <Text>Approve</Text> <Value>approve</Value> </SelectedButton> <Value /> <Data /> <Error /> </Result>
Fields:
FlowId (uuid): The unique identifier of the current flow. This will match the value you pass in your command unless you're executing a StartFlow which will generate and return a new FlowId.CommandName (string): The name of the command this result corresponds to.SelectedButton (object): The button selected by the user during a Prompt command.
Text (string): The text displayed on the button.Value (string): The value returned when the button is clicked.AccentColor (optional, string): The color of the button, e.g., "Red", "Green".Value (number): The value returned by a NumberCapture command. This will be the numeric value entered by the user.Data (object): Additional data returned as a result of a command.Error (string, optional): An error message if the command failed. If the command was successful, this field will not be returned (even though it is shown in the examples above as containing a blank value).StartFlow: Starts a flow and issues a new FlowId. Once used, this command cannot be used again until EndFlow is called.
Prompt: Displays a prompt to the user with buttons.
CardReaderCommand: Sends a command to the card reader and displays the tap/insert/swipe animation. See the Using the Card Reader section for more details.
Processing: Indicates that processing is ongoing and displays PamiPOP's wait state (color and icon changes).
SignatureCapture: Captures a signature from the user.
Data field on the Result object will contain a byte array (base64 encoded) of the signature image in PNG format.BarcodeCapture: Captures a barcode from the user via the camera.
Data field on the Result object will contain an array of scanned barcodes. An example of this in JSON is:
{
"FlowId": "2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"CommandName": "BarcodeCapture",
"Data": [
{
"Type": "QRCode",
"Value": "https://example.com"
},
{
"Type": "Ean13",
"Value": "1234567890123"
}
]
}
<Result> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <CommandName>BarcodeCapture</CommandName> <Data> <Barcode> <Type>QRCode</Type> <Value>https://example.com</Value> </Barcode> <Barcode> <Type>Ean13</Type> <Value>1234567890123</Value> </Barcode> </Data> </Result>
Type field indicates the type of barcode scanned. Potential values are:
Value field contains the actual scanned value of the barcode.NumberCapture: Captures a number the user types in on the keypad. A numeric mask should be passed in the Data property that signifies the number of integer digits and the number of decimals allowed. The _ (underscore) character indicates a numeric digit. All other characters will be added to the number automatically.
{
"FlowId": "2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"Name": "NumberCapture",
"DisplayText": "Enter tip amount",
"Data": { "Mask": "____.__" }
}
<?xml version="1.0" encoding="utf-8"?> <Command> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <Name>NumberCapture</Name> <DisplayText>Enter tip amount</DisplayText> <Data> <Mask>____.__</Mask> </Data> </Command>
The Result will return the formatted number as a string in the Value field and the unformatted number (numeric digits only) in the Digits field. If the user cancelled the capture request, both of these fields will be null or blank.
PhoneNumberCapture: Captures a phone number the user types in on the keypad. A string mask should be passed in the Data property that signifies the mask to use on the entered number.
_ (underscore) characters indicate a digit placeholder. All other characters are added to the phone number automatically.
{
"FlowId": "2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"Name": "PhoneNumberCapture",
"DisplayText": "Enter your phone number",
"Data": { "Mask": "(___) ___-____" }
}
<?xml version="1.0" encoding="utf-8"?> <Command> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <Name>PhoneNumberCapture</Name> <DisplayText>Enter your phone number</DisplayText> <Data> <Mask>(___) ___-____</Mask> </Data> </Command>
The Result will return the formatted phone number as a string in the PhoneNumber field and the unformatted (numeric digits only) phone number in the Digits field. If the user cancelled the capture request, both of these fields will be null or blank.
CustomFormCapture: Displays a custom form via a URL and waits for the user to complete it. Coming Soon
Approved: Indicates the transaction has been approved.
Declined: Indicates the transaction has been declined by the processor.
Cancelled: Indicates the transaction has been cancelled.
Suspended: Indicates the transaction has been suspended.
PresentCard: Shows the Tap/Insert/Swipe animation with a cancel button.
Data element contains the ShowCancelButton element which defaults to true
{
"FlowId": "2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"Name": "PresentCard",
"DisplayText": "Tap/Insert/Swipe Card",
"Data": {
"ShowCancelButton": true
}
}
<?xml version="1.0" encoding="utf-8"?> <Command> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <Name>PresentCard</Name> <DisplayText>Tap/Insert/Swipe Card</DisplayText> <Data> <ShowCancelButton>true</ShowCancelButton> </Data> </Command>
Please note you do not have to send this command if you are using the CardReaderCommand since that command already shows the animation. The PresentCard command is for use when card reader communication is not performed through the PamiPOP.
You should listen for a Result message with CommandName of PresentCard and a non-empty Error field to know when the user has tapped the cancel button.
RollingReceipt: Displays a rolling receipt on the screen. The Data field should contain the receipt items and total to display on the screen. For example:
{
"FlowId": "2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"Name": "RollingReceipt",
"DisplayText": "Your Receipt",
"Data": {
"Items": [
{ "Description": "Item 1", "Amount": "$5.00" },
{ "Description": "Item 2", "Amount": "$3.50" }
],
"Total": "$8.50",
"FontSize": "Medium",
"ItemsCanScrollOverHeader": false
}
}
<?xml version="1.0" encoding="utf-8"?> <Command> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <Name>RollingReceipt</Name> <DisplayText>Your Receipt</DisplayText> <Data> <Items> <Item> <Description>Item 1</Description> <Amount>$5.00</Amount> </Item> <Item> <Description>Item 2</Description> <Amount>$3.50</Amount> </Item> </Items> <Total>$8.50</Total> <FontSize>Medium</FontSize> <ItemsCanScrollOverHeader>false</ItemsCanScrollOverHeader> </Data> </Command>
Note that only the last few lines of the receipt in addition to the total will be shown on the screen. You should send multiple RollingReceipt commands to update the receipt. the Items element is intended to contain the entire receipt. PamiPOP will display as many items as it can from the bottom/end of the Items array.
The FontSize element can be one of Small, Medium, or Large and controls the size of the text shown on the screen. Default value is: Small
The ItemsCanScrollOverHeader element indicates whether the receipt items can scroll over the header area (where the DisplayText is shown) or if they should stop below it. Default value is: true
EndFlow: Ends the current flow and sets the PamiPOP back to advertisement mode. This command also clears the current FlowId so a new flow may be started with the StartFlow command.
ChangeToIdle: Changes the PamiPOP to an idle state and begins showing programmatic ads. This is a shortcut command that has the same effect as sending a StartFlow followed immediately by EndFlow and the FlowId element is NOT required for this Command.
A flow is a series of Command and Result messages that represent a complete interaction with the user. A flow must always begin with a StartFlow command and end with an EndFlow command. Between these two commands, you can send any number of other commands to guide the user through the desired interaction.
It is recommended to monitor for Result messages on a separate thread or task to ensure you can respond to user actions promptly. A common pattern is to treat interaction with PamiPOP as a pub-sub model where you send commands and listen for results asynchronously.
In this example, we will prompt the user to approve a "round up" transaction. The steps are as follows:
StartFlow (then wait for a FlowId to be returned)Prompt (then wait for a Result message with the selected button to be returned)EndFlow the WebSocket connection can be optionally closed after this command is sent.Here is an example of this workflow in C#:
async Task Main() { const string ipAddress = "192.168.1.54"; const string _apiKey = "Keep/This/Private/XYZ=="; const string _systemName = "Your System Name 1.0"; //create the web socket connection var webSocket = new ClientWebSocket(); webSocket.Options.SetRequestHeader("X-Mime-Type", "application/json"); webSocket.Options.SetRequestHeader("X-API-Key", _apiKey); webSocket.Options.SetRequestHeader("X-API-Client", _systemName); await webSocket.ConnectAsync(new Uri($"ws://{ipAddress}:7264/commands"), CancellationToken.None); //start the flow and get the flow id for future commands var startFlowCommand = new { Name = "StartFlow", DisplayText = "WELCOME!" }; await Send(webSocket, startFlowCommand); var startFlowResult = await WaitForResponse(webSocket); Console.WriteLine($"Got new FlowId: {startFlowResult.FlowId}"); //prompt the user to make a choice by tapping a button var promptCommand = new { FlowId = startFlowResult.FlowId, Name = "Prompt", DisplayText = "Would you like to round up for charity?", Buttons = new List<Button>() { new Button { Text = "Yes", Value = "yes", AccentColor = "Green" }, new Button { Text = "No", Value = "no", AccentColor = "Red" } } }; await Send(webSocket, promptCommand); var promptResult = await WaitForResponse(webSocket); Console.WriteLine($"The user chose the button with value '{promptResult.SelectedButton.Value}'"); //end the flow and close the web socket var endFlowCommand = new { FlowId = startFlowResult.FlowId, Name = "EndFlow", DisplayText = "GOODBYE!" }; await Send(webSocket, endFlowCommand); await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); } async Task Send(ClientWebSocket webSocket, object message) { var json = JsonSerializer.Serialize(message); await webSocket.SendAsync(Encoding.UTF8.GetBytes(json), WebSocketMessageType.Text, WebSocketMessageFlags.EndOfMessage, CancellationToken.None); } async Task<Result> WaitForResponse(ClientWebSocket webSocket) { var buffer = new ArraySegment<byte>(new byte[8192]); WebSocketReceiveResult result = null; using var memStream = new MemoryStream(); do { result = await webSocket.ReceiveAsync(buffer, CancellationToken.None); memStream.Write(buffer.Array, buffer.Offset, result.Count); } while (!result.EndOfMessage); memStream.Seek(0, SeekOrigin.Begin); return JsonSerializer.Deserialize<Result>(memStream); } public class Result { public Guid FlowId { get; set; } public string CommandName { get; set; } public Button? SelectedButton { get; set; } public string? Data { get; set; } public decimal? Value { get; set; } public string? Error { get; set; } } public class Button { public string Text { get; set; } public string Value { get; set; } public string AccentColor { get; set; } }
The output from the code above will look like this:
Got new FlowId: 21bb4f7e-9185-4304-a706-d9b406a16b2c
The user chose the button with value 'yes'
When capturing a custom amount from the user, you can use the NumberCapture command. An example of a flow that uses NumberCapture is below in C#:
async Task Main() { const string ipAddress = "192.168.1.54"; const string _apiKey = "Keep/This/Private/XYZ=="; const string _systemName = "Your System Name 1.0"; //create the web socket connection var webSocket = new ClientWebSocket(); webSocket.Options.SetRequestHeader("X-Mime-Type", "application/json"); webSocket.Options.SetRequestHeader("X-API-Key", _apiKey); webSocket.Options.SetRequestHeader("X-API-Client", _systemName); await webSocket.ConnectAsync(new Uri($"ws://{ipAddress}:7264/commands"), CancellationToken.None); //start the flow and get the flow id for future commands var startFlowCommand = new { Name = "StartFlow", DisplayText = "WELCOME!" }; await Send(webSocket, startFlowCommand); var startFlowResult = await WaitForResponse(webSocket); Console.WriteLine($"Got new FlowId: {startFlowResult.FlowId}"); //prompt the user to make a choice by tapping a button var promptCommand = new { FlowId = startFlowResult.FlowId, Name = "Prompt", DisplayText = "Would you like to add a tip?", Buttons = new List<Button>() { new Button { Text = "10% ($10.00)", Value = "10.00" }, new Button { Text = "15% ($15.00)", Value = "15.00" }, new Button { Text = "20% ($20.00)", Value = "20.00" }, new Button { Text = "25% ($25.00)", Value = "25.00" }, new Button { Text = "No Tip", Value = "0.00" }, new Button { Text = "Custom", Value = "custom" }, } }; await Send(webSocket, promptCommand); var promptResult = await WaitForResponse(webSocket); Console.WriteLine($"The user chose the button with value '{promptResult.SelectedButton.Value}'"); if (promptResult.SelectedButton.Value == "custom") { //ask the user to enter a numeric value for their custom tip var numberCaptureCommand = new { FlowId = startFlowResult.FlowId, Name = "NumberCapture", DisplayText = "Enter Tip Amount", Data = new { Mask = "_____.__" } //number of decimal places allowed (e.g. 1.11 allowed, but 1.111 not allowed) }; await Send(webSocket, numberCaptureCommand); var numberCaptureResult = await WaitForResponse(webSocket); Console.WriteLine($"The user entered {numberCaptureResult.Value} for a tip amount."); } var processingCommand = new { FlowId = startFlowResult.FlowId, Name = "Processing", DisplayText = "Processing. Please wait..." }; await Send(webSocket, processingCommand); //simulate doing some work / processing. await Task.Delay(5000); var approvedCommand = new { FlowId = startFlowResult.FlowId, Name = "Approved", DisplayText = "APPROVED" }; await Send(webSocket, approvedCommand); //wait to give time for the user to read the screen await Task.Delay(2000); //end the flow and close the web socket var endFlowCommand = new { FlowId = startFlowResult.FlowId, Name = "EndFlow", DisplayText = "THANK YOU. GOODBYE." }; await Send(webSocket, endFlowCommand); await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); }
The output from the example above looks like this:
Got new FlowId: 5fbf52ca-a9ef-4661-8f65-7f661c1305bd
The user chose the button with value 'custom'
The user entered 17.98 for a tip amount.
When using the CardReaderCommand, the Data element of a Command must contain a dictionary of key/value pairs that describe which card reader-specific command to run and what values to pass into its arguments. You should reference the section related to your card reader for more detailed key names, but the following keys are always required:
CommandName - the name of the card reader command to execute. This is specific to your card reader type, but usually mirrors the naming in the card reader's SDK.
{
"Name":"CardReaderCommand",
"FlowId":"2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"DisplayText":"TAP, INSERT or SWIPE",
"Data": {
"CommandName":"emv_autoAuthenticate",
"Authenticate":"true"
}
}
<Command> <Name>CardReaderCommand</Name> <FlowId>2e3c1f1a-4f6d-4b57-956a-800aaf3abba5</FlowId> <DisplayText>TAP, INSERT or SWIPE</DisplayText> <Data> <Element Key="CommandName" Value="emv_autoAuthenticate" /> <Element Key="Authenticate" Value="true" /> </Data> </Command>
The command above is the equivalent of calling IDT_Device.emv_autoAuthenticate(true) in the IDTech SDK.
Please note that all the values in the Data dictionary are strings. If your card reader SDK expects a different type (e.g., integer, boolean), the PamiPOP will convert the string to the appropriate type before passing it to the card reader's SDK.
Results from the CardReaderCommand will be returned in the Data element of the result message. The structure of this data is specific to your card reader type. Below is an example from an IDTech card reader. Please note the data in this example is a JSON string containing escaped JSON.
{
"CommandName":"CardReaderCommand",
"FlowId":"2e3c1f1a-4f6d-4b57-956a-800aaf3abba5",
"Data":"{\u0022State\u0022:\u0022TransactionData\u0022,\u0022Card\u0022:{\u0022msr_keyManagement\u0022:\u0022KEY_MANAGEMENT_UNKNOWN\u0022,\u0022Base64\u0022:\u0022VmlWT3RlY2gyAAIjAh3h3\\u002B4mAuEA3\\u002B4SCmKZSQCCABwgAMff70wGACUAAAAA3\\u002B9NKBEnICu1pe5BvJgOtiEEMrM0Koa2PoXdPJFLmb\\u002BVZWspIDk2CvTIW5zfgSkIMPDw8KDw/wDfgRYWG/8AABMAAAAAAAAAAAAAAAAAAAAAAP\\u002BBBYIBUk8HoAAAAAMQEFALVklTQSBDUkVESVRXoRNHYXPMzMwAQ9MRLMzMzMzMzMzMV8EY4d2e7etuEjj/3cjMkx2gqFtyRCztFSMQggIgAIQHoAAAAAMQEJUFAAAAAACWAQOaAyUJApwBAF8qAghAXy0CZW5fNAEBXzYBAp8CBgAAAAABAJ8DBgAAAAAAAJ8GB6AAAAADEBCfCQIAAp8QBwYBCgOgAACfFQIAAJ8aAghAnxsEAAAAAJ8eCDZUNDEwNzU0nyEDFBk1nyYIrV7/b2tuv\\u002BKfJwGAnzMDYCjInzQDPwAAnzUBIZ82AgDPnzcENf21uZ85AQefQAVgAAAwAJ9BBAAAATmfTiIxMDcyMSBXYWxrZXIgU3QuIEN5cHJlc3MsIENBICxVU0Eun1oFMQhACECfZgQygEAAn2wCOACfbgQgcAAA34EmBgAAAABQAN/uTAEG/\\u002B4BCt/uMAEA3\\u002B5bAQXf7BweCAMABP8AGwAAABP/AAAAAAAAAAAAAAAAAAAAAP8A3\\u002BwRIAnrN8qOeGl8pom3E4ze158rtRfDDCMX/16DR4/6HJuqhCY=\u0022,\u0022Event\u0022:\u0022EVENT_TRANSACTION_DATA_EMV_DATA\u0022,\u0022Notification\u0022:\u0022EVENT_NOTIFICATION_UNKNOWN\u0022,\u0022msr_track1Length\u0022:0,\u0022msr_track2Length\u0022:0,\u0022msr_track3Length\u0022:0,\u0022msr_cardType\u0022:\u0022CAPTURE_ENCODE_TYPE_UNKNOWN\u0022,\u0022msr_captureEncodeStatus\u0022:0,\u0022captureEncryptType\u0022:\u0022CAPTURE_ENCRYPT_TYPE_TDES\u0022,\u0022captureEncryptTypeEMV\u0022:\u0022CAPTURE_ENCRYPT_TYPE_UNKNOWN\u0022,\u0022captureCardType\u0022:\u0022CAPTURE_CARD_TYPE_CTLS_EMV\u0022,\u0022msr_errorCode\u0022:0,\u0022emv_rfStateCode\u0022:0,\u0022iccPresent\u0022:0,\u0022msr_keyVariantType\u0022:\u0022KEY_VARIANT_TYPE_UNKNOWN\u0022,\u0022isCTLS\u0022:1,\u0022ctlsApplication\u0022:\u0022CTLS_APPLICATION_NONE\u0022,\u0022emv_unencryptedTags\u0022:\u002234EWFhv/AAATAAAAAAAAAAAAAAAAAAAAAADfgSkIMPDw8KDw/wDf7BEgCes3yo54aXymibcTjN7Xnyu1F8MMIxf/XoNHj/ocm6rf7BweCAMABP8AGwAAABP/AAAAAAAAAAAAAAAAAAAAAP8A3\u002B4SCmKZSQCCABwgAMff7iYC4QDf70wGACUAAAAA3\u002B9NKBEnICu1pe5BvJgOtiEEMrM0Koa2PoXdPJFLmb\u002BVZWspIDk2CvTIW5z/gQWCAVJPB6AAAAADEBBQC1ZJU0EgQ1JFRElUV6ETR2FzzMzMAEPTESzMzMzMzMzMzFfBGOHdnu3rbhI4/93IzJMdoKhbckQs7RUjEIICIACEB6AAAAADEBCVBQAAAAAAlgEDmgMlCQKcAQBfKgIIQF8tAmVuXzQBAV82AQKfAgYAAAAAAQCfAwYAAAAAAACfBgegAAAAAxAQnwkCAAKfEAcGAQoDoAAAnxUCAACfGgIIQJ8bBAAAAACfHgg2VDQxMDc1NJ8hAxQZNZ8mCK1e/29rbr/inycBgJ8zA2AoyJ80Az8AAJ81ASGfNgIAz583BDX9tbmfOQEHn0AFYAAAMACfQQQAAAE5n04iMTA3MjEgV2Fsa2VyIFN0LiBDeXByZXNzLCBDQSAsVVNBLp9aBTEIQAhAn2YEMoBAAJ9sAjgAn24EIHAAAN\u002BBJgYAAAAAUADf7kwBBv/uAQrf7jABAN/uWwEF\u0022,\u0022emv_resultCode\u0022:\u0022EMV_RESULT_CODE_FAILED\u0022,\u0022emv_hasAdvise\u0022:false,\u0022emv_hasReversal\u0022:false,\u0022SW1\u0022:0,\u0022SW2\u0022:0,\u0022hasMACVerificationData\u0022:false,\u0022emv_transaction_Error_Code\u0022:0,\u0022emv_RF_State\u0022:\u0022RF_STATE_NONE\u0022,\u0022emv_ESC\u0022:0,\u0022emv_appErrorFn\u0022:\u0022CEMV_APP_ERROR_FN_NONE\u0022,\u0022emv_appErrorState\u0022:\u0022CEMV_APP_ERROR_STATE_NONE\u0022,\u0022captured_SHA256\u0022:false},\u0022TransactionResultCode\u0022:\u0022RETURN_CODE_DO_SUCCESS\u0022,\u0022Ip\u0022:\u0022VP3350:USB-1\u0022}"
}
If your PamiPOP is equipped with an IDTech card reader, you can send commands to it that are equivalent to those exposed through IDTech's SDK. Supported card reader commands and their associated key/value dictionary elements are:
CommandName: device_rebootDevice - Reboots the card reader. No additional parameters are required.CommandName: device_sendConfiguration
FilePath: A string containing the contents of the JSON configuration file you want to store on the card reader. Please note, this value must be more than 500 characters so the IDTech SDK uses it as file contents instead of a file path.Type: A string representing a value from the VC_OPERATION_TYPE enum in the IDTech SDK.MatchFW: A boolean valueCommandName: emv_allowFallback
Allow: A boolean valueCommandName: emv_autoAuthenticate
Authenticate: A boolean valueCommandName: emv_forceOnline
Allow: A boolean valueCommandName: device_startTransaction
Amount: A double valueAmtOther: A double valueExponent: An integer valueType: An integer valueTimeout: An integer valueTags: A hex string representing a byte arrayOptions: A hex string representing a byte arrayIsFastEMV: A boolean valueCommandName: emv_cancelTransaction - No additional parameters are required.CommandName: emv_authenticateTransaction
UpdatedTLV: A hex string representing a byte arrayCommandName: emv_completeTransaction
CommError: A boolean valueAuthCode: A hex string representing a byte arrayIAD: A hex string representing a byte arrayTLVScripts: A hex string representing a byte arrayTLV: A hex string representing a byte arrayCommandName: ReturnAllData - this is a special command that tells the PamiPOP to return all data from the IDTech callback method (turned off by default). This can result in a large number of Result messages and cause extra "noise" but may be used during development to diagnose issues or see raw data messages. As mentioned, by default this is set to false. You will only receive card and emvCallback messages. This setting persists even after rebooting the PamiPOP. To turn it back off, call it again with ReturnAll set to false.
ReturnAll: A boolean valueCommandName: msr_startMSRSwipe
Timeout: An integer valueCommandName: msr_cancelMSRSwipe - No additional parameters are required.CommandName: ctls_startTransaction
Amount: A double valueAmtOther: A double valueExponent: An integer valueType: An integer valueTimeout: An integer valueForceOnline: A boolean valueIsFastEMV: A boolean valueCommandName: ctls_cancelTransaction - No additional parameters are required.It is important for you to understand how the IDTech SDK works, as the commands you send to the PamiPOP will behave as if you were using the SDK directly.
For IDTech card readers, please note the Data element returned from a CardReaderCommand will be a string containing a JSON serialized object. This will be JSON regardless of which Content-Type you have chosen for your WebSocket connection.
IDTech card readers will return the following object in the Data element of the result:
{
State: <DeviceState from the SDK>,
Data: <Base64-encoded byte array>,
Card: <IDTTransactionData from the SDK>,
EmvCallback: <EMV_Callback from the SDK>,
TransactionResultCode: <RETURN_CODE from the SDK>,
Ip: <String containing the device name>
}
This is the equivalent of the delegate signature used when registering for callbacks via the IDT_Device.setCallbackIP SDK method. Each of the elements can be deserialized back into their associated SDK types.