Card-present payments over the API
What a terminal application sends on the transactions API for a magnetic stripe, chip, or contactless read, which reader encryption needs setup first, what each processor does with healthcare amounts, how to tokenize a read without charging it, how to identify the terminal, and how to attribute a webhook to it.
This guide is for developers writing a terminal application that reads a card at a counter, a lane, or a handheld device and sends the read to WinkPG. It covers the fields that make a transaction card-present, what reader encryption needs set up before the first sale, how to tell which terminal took a transaction, and how a webhook receiver attributes an event to that terminal.
Read Quickstart: Direct API first if you haven't taken a payment through the transactions API yet. Everything here is an addition to that request.
When this applies
This guide applies when the terminal application calls the transactions API directly and WinkPG authorizes the payment with the processor. The terminal reads the card, builds the create request, and reads the result from the response.
There's nothing terminal-specific to set up on the API side:
- No terminal endpoint. A card-present sale is a
POSTto/api/transactions, the same create request every other payment uses. - No device registration. WinkPG doesn't keep a list of devices, and a new terminal doesn't need to be enrolled before it sends its first sale.
- No per-device credential. The merchant's API key, with the
transactions:writescope, is the whole handshake. Every terminal the merchant runs can send with the same key, or you can issue one key per site or per device if you want to revoke one without affecting the others.
A terminal that authorizes with the processor itself and then reports the finished transaction to WinkPG is a different model, and this guide doesn't describe it.
What the merchant configures first
Two things on the merchant's processor profile decide whether a card read reaches the processor as a card-present transaction. Both are set once, by whoever boards the merchant, and apply to every terminal. Neither is configured per device.
- An active processor profile with a card-present industry, such as Retail or Food/Restaurant. Routing doesn't look at the entry mode, so a card-present request goes to whichever profile the merchant's routing selects. The processor message is built for that profile's industry: on a profile set to Electronic Commerce or Direct Marketing, the message isn't a card-present one, and the chip data may not be sent at all.
- The processor's terminal number on that profile. Depending on the processor, the field is labeled Terminal Number or Terminal ID. It's the value the processor assigned to the merchant's terminal configuration, and every terminal application sending through the profile shares it.
A merchant with no active processor profile can't take any payment. The create request comes back with the ProcessorNotConfigured result code.
The request
A card-present request is the ordinary create request with three additions: the entry mode, the data the reader produced, and a deviceData block describing the device.
Entry mode
cardData.entryMode says how the card was read on this transaction. It's what makes the transaction card-present, so always send it from a terminal.
entryMode |
Send it when |
|---|---|
Manual |
The card number was keyed on the terminal. A keyed number is processed as a card-not-present entry unless you also set cardholderPresence (see below). |
UnencryptedCardReaderSwipe |
The magnetic stripe was read and the reader produced cleartext track data. |
EncryptedCardReaderSwipe |
The magnetic stripe was read and the reader encrypted the track data. |
Icc |
The chip was read by contact (a dip). |
Proximity |
The card or phone was tapped (contactless). |
Fallback |
The chip couldn't be read, so the magnetic stripe was read instead. |
TechnicalFallback |
The chip couldn't be read because of a fault in the chip or the reader, so the magnetic stripe was read instead. |
EmptyCandidateFallback |
The chip was read, but the card and the terminal share no application, so the magnetic stripe was read instead. |
Every value except Manual classifies the transaction as card-present. Leaving entryMode out stores it as Unknown, and the processor message then infers presence from the profile's industry instead of from the read. For a terminal read, that inference is the wrong place to start.
Cleartext read fields
When the reader hands you the card data in the clear, send it on these fields. The encrypted case is in Encrypted reads below.
| Field | Carries | Required |
|---|---|---|
cardData.trackData |
The track data from a magnetic stripe read. | For UnencryptedCardReaderSwipe, unless you send emvData instead. |
cardData.emvData |
The chip data the reader produced, as a TLV string. | For Icc and Proximity. |
deviceData.cvmResults |
The cardholder verification method the reader used: 1 for offline PIN, 2 for online PIN, 3 for signature. |
No. Send it when the reader reports it and the chip data has no tag 9F34. WinkPG reads it only as a fallback for that tag. |
A request whose fields don't match its entry mode is rejected with HTTP 400 and a CardData: code. For example, Icc with no emvData returns CardData:EmvDataRequiredForIccOrProximity, and UnencryptedCardReaderSwipe with neither trackData nor emvData returns CardData:TrackOrEmvRequiredForUnencryptedSwipe.
Cardholder presence
cardholderPresence describes whether the cardholder is at the point of sale. That's a different question from how the card was read. Its values are CardPresent, MOTO, ECommerce, and Recurring.
Leave it unset for a magnetic stripe, chip, contactless, or fallback read. WinkPG infers CardPresent from any of those entry modes, which is the right answer.
Set it when the inference would be wrong. The common case is a keyed entry at a counter: with entryMode set to Manual, WinkPG infers MOTO, so a cardholder standing at the terminal needs cardholderPresence set to CardPresent explicitly.
cardholderPresence doesn't choose the channel the processor receives. On Fiserv and TSYS, the industry on the merchant's processor profile does: a retail profile sends card-present transactions, a direct marketing profile sends MOTO transactions, and an electronic commerce profile sends e-commerce transactions. When you set cardholderPresence to CardPresent, MOTO, or ECommerce on an authorization or a sale, and the profile sends a different channel, the transaction is processed as the profile's channel and responseData.cardholderPresenceProfileMismatch is true. It's false when the two agree, and it's absent when there's nothing to compare. A mismatch doesn't change the outcome, so if you see it, ask the merchant's administrator to check the processor profile.
Terminal capability
cardData.terminalCapability declares what the terminal hardware can read, which can differ from how this card was read. Its values are MagneticStripe, MagneticStripeWithPin, Chip, ChipWithPin, ChipContactless, and ChipContactlessWithPin.
It's optional. Leave it unset and the processor message carries the processor's default capability. Set it when the terminal is stripe-only, so the processor doesn't see a chip capability the device doesn't have. A stripe-only capability combined with a chip, contactless, or fallback entry mode is a contradiction, and it's rejected with HTTP 400 and CardData:EntryModeIncompatibleWithTerminalCapability.
Device data
The deviceData block holds two kinds of field, and they behave differently.
These three fields are telemetry. WinkPG stores them and shows them back, and they don't change how the payment is processed:
| Field | Carries |
|---|---|
deviceData.serialNumber |
The device's serial number. This is the field that identifies the physical device; see Identifying the terminal. |
deviceData.sdkVersion |
The version of the terminal application or the reader SDK. |
deviceData.batteryLevel |
The battery level, as a whole-number percentage, for a device that reports one. |
These two fields change processing:
| Field | Effect |
|---|---|
deviceData.isEncrypted |
true tells WinkPG that every card data field on the request holds ciphertext, including emvData. Defaults to false. |
deviceData.encryptionType |
The scheme the reader encrypted with. It selects the decryption path; see Encrypted reads. |
[!WARNING] Setting
isEncryptedtotrueon a cleartext read makes WinkPG treatemvDataas an opaque encrypted payload instead of TLV. The chip data isn't parsed, so the card application isn't identified from it.
Example
A cleartext chip read, sent from lane 4:
curl -X POST "https://your-gateway-host/api/transactions" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Sale",
"idempotencyKey": "lane-04-000123",
"merchantReference": "TICKET-000123",
"register": {
"id": "00000000-0000-0000-0000-000000000004",
"name": "Lane 4"
},
"cardData": {
"entryMode": "Icc",
"emvData": "EMV_TLV_FROM_THE_READER"
},
"deviceData": {
"serialNumber": "DEVICE_SERIAL_NUMBER",
"sdkVersion": "TERMINAL_APP_VERSION",
"isEncrypted": false
},
"invoiceData": {
"amounts": { "base": 10.00, "total": 10.00 }
}
}'
EMV_TLV_FROM_THE_READER stands for the TLV string the reader returned for this one read. Never build it by hand or reuse one from an earlier read: the chip produces a new cryptogram for each authorization.
Encrypted reads
When the reader encrypts the card data, set deviceData.isEncrypted to true, set deviceData.encryptionType to the reader's scheme, and send the key serial number the reader reports on cardData.keySerialNumber. The ciphertext goes on these fields:
cardData.encryptedTrack2Data, plusencryptedTrack1Datawhen the reader produces it. This is the only payload WinkPG decrypts itself, so every scheme it decrypts needs it, including on a chip read.cardData.emvData, for an encrypted chip or contactless read. WinkPG never decrypts this field. It's forwarded as ciphertext on the schemes that forward, and ignored by the decryption on the schemes that don't.
Whether anything has to be set up first depends on the scheme. The comparison on encryptionType ignores case and surrounding spaces.
encryptionType |
What it needs before the first sale | What WinkPG does with the read |
|---|---|---|
MAGENSA |
Nothing on the WinkPG side. | Decrypts nothing. Forwards the encrypted tracks, the encrypted chip data, and the key serial number to the decryption service in front of the processor. |
PAXDUKPT |
The reader is injected with WinkPG's base derivation key. That's a provisioning step with the key injection facility, not an API call. | Decrypts the encrypted track 2 data with that key before authorization. If the merchant is boarded on a payment encryption provider, the read is decrypted through the provider instead, on the same terms as DUKPT below. |
DUKPT or ONGUARD |
An administrator boards the merchant on a payment encryption provider and adds a key entry for each key serial identifier the merchant's readers use. | Decrypts through the provider, using the key entry the read's key serial number matches. A key serial number with no matching entry is refused with HTTP 403 and Decryption:UnknownKeySerialIdentifier, and a read with no encrypted track data or no key serial number with HTTP 400 and Decryption:InvalidPayload. Neither creates a transaction. A merchant that isn't boarded has the read forwarded to the processor still encrypted. |
| Any other value | Nothing on the WinkPG side. | Decrypts nothing. Forwards the payload to the processor as sent. |
PAXDUKPT reads the key serial number from a different place than the other schemes:
- When the merchant isn't boarded on a provider, the key serial number is read from
deviceData.serialNumberand nowhere else. A read with an emptyserialNumberisn't decrypted. - When the merchant is boarded,
cardData.keySerialNumberis read first, anddeviceData.serialNumberis the fallback.
Every other scheme reads cardData.keySerialNumber only, and deviceData.serialNumber stays the device's own serial number.
If the merchant is boarded on a payment encryption provider, the Encrypted card reader payments guide in the help center covers the decryption record on the response and every refusal code to handle.
Online PIN
For a PIN debit or EBT sale, send the encrypted PIN block the PIN pad produced on cardData.pin, together with the key serial number the block was encrypted under. WinkPG never decrypts or translates a PIN block. It forwards the block and its key serial number to the processor unchanged, so the PIN pad's PIN key has to be one the processor can derive.
Which field carries that key serial number depends on how the reader was injected:
| Reader keys | Send |
|---|---|
| One key for card data and PINs | The key serial number on cardData.keySerialNumber. Leave cardData.pinKeySerialNumber out. |
| Separate data and PIN keys | The data key's serial number on cardData.keySerialNumber, and the PIN key's on cardData.pinKeySerialNumber. |
Separate keys are the usual arrangement when a payment encryption provider holds the data key and the processor owns the PIN key. keySerialNumber is the one WinkPG uses to decrypt the card data, and pinKeySerialNumber travels with the PIN block. pinKeySerialNumber is never used to decrypt anything, so it needs no key entry on the merchant's payment encryption provider.
pinKeySerialNumber is 10 to 24 hexadecimal characters, with or without the reader's leading F padding. Any other value is rejected with HTTP 400 and CardData:PinKeySerialNumberInvalid.
Healthcare (FSA and HSA) amounts
For a sale on a healthcare card, send the healthcare part of the basket on fsa, one amount per category, and set fsa.partialAuthSupport to true. A healthcare card often covers only part of the sale, and a partial approval lets that part approve. Each amount is the part of the sale in that category, not the sale total, and the healthcare total WinkPG sends is the sum of qhpAmount, rxAmount, visionAmount, dentalAmount, clinicalAmount, and copayAmount.
"fsa": {
"rxAmount": 12.34,
"copayAmount": 5.00,
"partialAuthSupport": true
}
What reaches the processor depends on the processor:
| Field | Fiserv | TSYS | Chase Paymentech |
|---|---|---|---|
| Healthcare total | Sent on Visa and Mastercard | Sent on Visa and Mastercard | Not sent |
rxAmount |
Sent on Visa | Sent | Not sent |
visionAmount |
Sent on Visa | Sent | Not sent |
clinicalAmount |
Sent on Visa | Sent | Not sent |
dentalAmount |
Sent on Visa | Sent | Not sent |
copayAmount |
Sent on Visa and Mastercard | In the total only | Not sent |
qhpAmount |
In the total only | In the total only | Not sent |
transitAmount |
Not sent | Not sent | Not sent |
partialAuthSupport |
Honored | Honored | Honored |
On Fiserv, the amounts ride a sale, an authorization, and an online refund. On TSYS:
- The amounts are sent only once healthcare amounts are enabled for your deployment. Until then, a TSYS sale carrying them is authorized for its full amount without the healthcare detail.
- They ride a sale or an authorization from a magnetic stripe read, a keyed card, or a card-not-present payment. A chip read, a refund, a stored-credential or digital wallet payment, a 3-D Secure authenticated payment, a payment with cash back, and a card-not-present payment at a retail or restaurant merchant are sent without them.
- At most three of
rxAmount,visionAmount,clinicalAmount, anddentalAmountare itemized, taken in that order. When all four are present,dentalAmountis sent inside the total. - Sending the amounts states that the merchant checked the purchase against an Inventory Information Approval System (IIAS). Send them only from a point of sale that does.
transitAmount isn't healthcare spend, so it isn't part of the healthcare total, and no processor receives it. On every processor, an amount that isn't sent leaves the sale unchanged.
What comes back and what's stored
The response to the create request, and every later GET /api/transactions/{transactionId}, carries the transaction as stored. For a card-present sale, that includes:
cardData.entryMode, as you sent it. It also classifies the transaction under POS Terminal in the Entry Mode filter on the transactions list.- The whole
deviceDatablock, theregister, and themerchantReference. - Presence flags in place of the raw read:
cardData.hasEmvData,cardData.hasTrackData,cardData.hasEncryptedTrackData, andcardData.hasPin.
The raw track data, chip data, encrypted tracks, PIN, and reader-generated dynamic verification value are never stored. They're used for the authorization and dropped, so a read can't be recovered from the transaction afterward, and the presence flags are the only record that the request carried them.
An abridged response for the example above:
{
"id": "6f3b2c18-0a4d-4a9e-9d4f-2b71c2f0a911",
"transactionType": "Sale",
"merchantReference": "TICKET-000123",
"register": {
"id": "00000000-0000-0000-0000-000000000004",
"name": "Lane 4"
},
"cardData": {
"entryMode": "Icc",
"hasEmvData": true,
"hasTrackData": false,
"hasEncryptedTrackData": false,
"hasPin": false
},
"deviceData": {
"serialNumber": "DEVICE_SERIAL_NUMBER",
"sdkVersion": "TERMINAL_APP_VERSION",
"isEncrypted": false
}
}
Tokenize a terminal read
A terminal can store a card without charging it, so the merchant can charge it later. There's no single request that does this from a read:
POST /api/tokens/create-standalone-asynctakes a card number in the clear and nodeviceData. It doesn't decrypt anything or read track or chip data, so it can't tokenize a read.- The transactions API refuses
saveCardRequested, so a payment can't store its own card.
Make two calls instead:
- A zero-dollar
Authorizationthat carries the read. WinkPG decrypts the read before anything else looks at the card, takes the card number and expiration date from the decrypted track, and verifies the card with the processor without holding any funds. POST /api/tokens/create-from-transaction-asyncwith that transaction's identifier. WinkPG stores the card number it took from the read and returns a token.
Step 1: Verify the card for zero
Send the read on an ordinary create request, with transactionType set to Authorization and every amount set to zero. An encrypted read carries the same fields it carries on a sale (see Encrypted reads): deviceData.isEncrypted, deviceData.encryptionType, cardData.keySerialNumber, cardData.encryptedTrack2Data, and cardData.encryptedTrack1Data when the reader produces it. Add cardData.emvData for a chip or contactless read.
curl -X POST "https://your-gateway-host/api/transactions" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transactionType": "Authorization",
"idempotencyKey": "lane-04-verify-000124",
"merchantReference": "TICKET-000124",
"register": {
"id": "00000000-0000-0000-0000-000000000004",
"name": "Lane 4"
},
"cardData": {
"entryMode": "Icc",
"emvData": "ENCRYPTED_EMV_FROM_THE_READER",
"encryptedTrack2Data": "ENCRYPTED_TRACK2_FROM_THE_READER",
"keySerialNumber": "KSN_FROM_THE_READER"
},
"deviceData": {
"serialNumber": "DEVICE_SERIAL_NUMBER",
"sdkVersion": "TERMINAL_APP_VERSION",
"isEncrypted": true,
"encryptionType": "DUKPT"
},
"invoiceData": {
"amounts": { "base": 0.00, "total": 0.00 }
}
}'
Keep the transaction id from the response, and go on to step 2 only when responseData.resultCode reads Ok. Step 2 stores a card only from a transaction the processor approved: it refuses a declined verification, one that returned an error, or one with no result yet, with HTTP 400 and Transactions:TokenMintTransactionNotApproved, and stores nothing. Reading the result first saves that call. When the verification is declined, run a new one rather than repeating step 2.
A zero-dollar authorization holds no funds, never settles, and has nothing to void. WinkPG refuses a Void against it with VOID_NOT_APPLICABLE_ZERO_DOLLAR_VERIFICATION. It needs a processor that supports zero-dollar account verification, the same capability the Save Payment Method option on a hosted payment page needs.
On TSYS, a zero-dollar magnetic stripe or keyed read is sent to the processor as an account verification. A zero-dollar chip or contactless read is sent as an authorization for zero with the chip data.
Step 2: Create the token from the transaction
Pass the transaction's identifier and your merchant identifier on the query string. Add customerId when the card belongs to a customer.
curl -X POST "https://your-gateway-host/api/tokens/create-from-transaction-async?transactionId=TRANSACTION_ID&merchantId=MERCHANT_ID&customerId=CUSTOMER_ID" \
-H "api-key: YOUR_API_KEY"
The response carries the token's publicReference, which is the value to send as tokenData.token on a later charge, and a masked copy of what was stored. An abridged response:
{
"publicReference": "pt_4mRt8XwQ2nLc6ZbK9hJ3vD7s",
"maskedPaymentDetails": {
"cardData": {
"cardNumber": "**** **** **** 1111"
}
},
"type": "Card"
}
Reusing a stored payment method with payment tokens covers charging the stored card later.
What's stored and what isn't
The token holds the card number and expiration date WinkPG took from the read, together with the name on card, billing address, email, and phone you sent on the authorization. It never holds the track data, the chip data, the encrypted tracks, the key serial number, the PIN, or a cryptogram. Those were used for the verification and dropped, the same as on a sale.
Whether there's a card number to store depends on whether WinkPG decrypted the read:
| Read | Can it be tokenized? |
|---|---|
DUKPT or ONGUARD |
Yes, when the merchant is boarded on a payment encryption provider. A merchant that isn't boarded has the read forwarded to the processor still encrypted, which leaves no card number to store. |
PAXDUKPT |
Yes, when WinkPG decrypts the read. On a merchant that isn't boarded on a provider, a read that can't be decrypted is forwarded still encrypted and leaves no card number to store. |
MAGENSA |
No. WinkPG forwards the read to the decryption service without decrypting it, so it never has the card number. The transaction holds only a masked number. |
Any other encryptionType |
No, for the same reason: the payload is forwarded as sent. |
| A cleartext magnetic stripe read | Yes. The card number and expiration date come from the track data. |
| A cleartext chip or contactless read with no track data | No. WinkPG doesn't take the card number from chip data. |
An authorization sent over the transactions API holds no other credential WinkPG could store in place of the card number, because the API doesn't let a payment ask to save its card. So when there's no card number, the create call is refused with HTTP 400 and Transactions:TokenMintTenderMissing, and no token is created.
Record the cardholder's consent
A terminal can ask the cardholder, on its screen or for a signature, to let the merchant store the card and charge it later without them present. Card network rules require that agreement before a merchant-initiated charge, and WinkPG checks for it. Record it on the same create call: send the customerId of the customer the card is for, and send the agreement in the body as storedCredentialConsent. It holds when the cardholder agreed (capturedAtUtc), how (method, such as OnScreen or SignedForm), the uses they agreed to (allowedUsages), the version of the consent text the terminal showed (consentTextVersion), and your own reference to the signature or log line you kept (evidenceReference).
curl -X POST "https://your-gateway-host/api/tokens/create-from-transaction-async?transactionId=TRANSACTION_ID&merchantId=MERCHANT_ID&customerId=CUSTOMER_ID" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"storedCredentialConsent": {
"capturedAtUtc": "2026-10-14T15:30:00Z",
"method": "OnScreen",
"allowedUsages": "UnscheduledCOF",
"consentTextVersion": "CONSENT_TEXT_VERSION_YOU_SHOWED",
"evidenceReference": "terminal-7-sig-000481"
}
}'
The card is stored on the customer, the consent is recorded against it, and the response also carries the storedCredentialConsentId. Recording a consent needs the StoredCredentialConsents.Create permission as well as the token permission. Keep the signature image or recording on your own system, and send only your reference to it. The consent appears in the back office under Stored Credential Consents, captured via Api, with your reference as its Merchant Reference.
If you create the token without a consent, the card has none until someone records one. An operator does that in the back office, from the Stored Credential Consents section of the customer, and until then WinkPG refuses a merchant-initiated charge against the card. The Stored credential consents guide in the help center covers the other ways a consent is captured, and how to revoke one.
Completing a chip transaction
After an online chip or contactless authorization, the card expects the issuer's answer before it produces its final cryptogram. WinkPG returns that answer on the create response as responseData.emv, on an approval and on a decline. Pass it to the terminal's EMV kernel to complete the transaction with the card.
| Field | Carries |
|---|---|
responseData.emv.issuerAuthenticationData |
Tag 91, the value only. The card uses it to authenticate the issuer's response. |
responseData.emv.issuerScriptTemplate1 |
Every tag 71 template the issuer sent, each with its tag and length, in the order received. The kernel runs these before the final GENERATE AC. |
responseData.emv.issuerScriptTemplate2 |
Every tag 72 template the issuer sent, each with its tag and length, in the order received. The kernel runs these after the final GENERATE AC. |
responseData.emv.authorizationResponseCode |
Tag 8A, the value only. An approval reads 3030. |
responseData.emv.rawTlv |
The whole EMV response the processor returned, including any tag not broken out above. |
Every value is uppercase hexadecimal, and any field the issuer didn't send is left out. A decline can still carry issuer scripts, such as an instruction to block the card, so read responseData.emv on every outcome, not only on an approval.
responseData.emv is absent when the processor returned no EMV response data, which is the case for every read that isn't chip or contactless. On TSYS, it's also absent unless the merchant's processor profile sets EMV Response Data Support to Supported. Whoever boards the merchant sets it once, on the profile.
A later GET /api/transactions/{transactionId} returns the same block, until a void or reversal replaces the response data with its own. Webhooks don't carry it. Read it from the create response, which is when the terminal needs it.
An abridged decline that carries an issuer script:
{
"responseData": {
"resultCode": "Decline",
"emv": {
"issuerAuthenticationData": "1A2B3C4D5E6F70810012",
"issuerScriptTemplate1": "71169F180411223344860D8424000008AABBCCDDEEFF0011",
"authorizationResponseCode": "3035",
"rawTlv": "8A023035910A1A2B3C4D5E6F7081001271169F180411223344860D8424000008AABBCCDDEEFF0011"
}
}
}
The values above are illustrative. Pass the kernel exactly what the response carries, and never reuse a value from an earlier transaction.
When the card declines after an online approval
The issuer can approve a chip authorization online and the card can still decline the transaction at the final GENERATE AC, for example when the card rejects the issuer's authentication data. The sale didn't complete, but the issuer is holding the funds. Release the hold with a full Reversal on the operations endpoint, with reversalReasonCode set to ChipDeclinedAfterOnlineApproval:
curl -X POST \
"https://your-gateway-host/api/transactions/by-merchant/{merchantId}/{transactionId}/operations" \
-H "api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operationType": "Reversal",
"reversalReasonCode": "ChipDeclinedAfterOnlineApproval",
"reason": "Card declined at the final GENERATE AC"
}'
Leave out amount, so the whole authorization is released. The reversal carries no chip data, and the operations endpoint takes none: the processor finds the original authorization by its identifiers.
How the processor hears the reason depends on the processor:
| Processor | What the reversal states |
|---|---|
| TSYS | Message reason code 2517, chip declined after online issuer approval. |
| Chase Paymentech | The card-declined reversal reason. |
| Fiserv | An ordinary void. Fiserv defines no reason for this case. |
The transaction history records the reason on every processor. For the other reason codes, see Refunds, voids, and reversals.
When the terminal can't reach WinkPG
WinkPG has no store-and-forward submission. There's no request that carries a sale the card approved offline, with its chip data and offline approval code, for the gateway to settle later. Configure the terminal to authorize online only, and to decline when it can't reach WinkPG, rather than approving under a floor limit.
A Force with originalTransaction.authCode isn't a substitute. It's built for a voice authorization the merchant obtained by phone, not for a card read, and it isn't processed as a chip, PIN debit, or healthcare (FSA or HSA) sale. Don't send cardData.pin or any non-zero fsa amount on a Force. Depending on the deployment, a Force that carries either is refused with HTTP 400 and the code Transactions:ForceTenderNotSupported. Submit a PIN debit or healthcare sale online while the cardholder is present.
Identifying the terminal
Three native fields identify where a transaction came from. Each answers a different question, and you can send all three on the same request.
| Field | Identifies | Where you see it |
|---|---|---|
deviceData.serialNumber |
The physical device. | Returned on GET and on lifecycle webhooks. Shown as Serial Number in the Device section of the transaction's detail page. |
register.id and register.name |
The lane, till, or station, independent of which device is in it today. | Returned on GET and on lifecycle webhooks. Receipts print the name after TID:. |
merchantReference |
The merchant's own ticket, order, or invoice number. | Returned on GET and on lifecycle webhooks. It's also carried to the acquirer on the clearing record, so it's the value to reconcile against on the acquirer's transaction report. |
Keep merchantReference free of card numbers and other sensitive data, because it's stored on the transaction and sent to the acquirer.
For a PAXDUKPT reader on a merchant that isn't boarded, deviceData.serialNumber carries the key serial number (see Encrypted reads), so use register to identify the terminal instead.
Custom fields are a fourth option, for any other value you want to search or report on. The field has to be defined and turned on for the merchant first: once a merchant has at least one custom field turned on, a create request naming a field that isn't one of them is rejected with HTTP 400. See Transaction custom fields for how to define them.
Webhooks
Every transaction lifecycle event fires for a card-present transaction exactly as it does for any other. WinkPG doesn't filter events by source or entry mode, so a subscription that receives Transaction.Authorized for an online payment receives it for a tap at lane 4 too.
The lifecycle event body carries the transaction identifier, the merchant identifier, the status, the amounts, the result and decline reason codes, and on Transaction.Settled the batch identifiers. It also carries the values that attribute the transaction to a terminal and to your own sale. They use the same names and nesting as GET /api/transactions/{transactionId}, written in the event body's PascalCase, so a case-insensitive parser reads both:
| Field in the event body | Field on GET |
Carries |
|---|---|---|
IdempotencyKey |
idempotencyKey |
The idempotency key the transaction was created with. For a terminal integration, this is usually your own sale ID. |
MerchantReference |
merchantReference |
The merchant's own ticket, order, or invoice number. |
DeviceData.SerialNumber |
deviceData.serialNumber |
The device serial number. In the event body, DeviceData holds the serial number only. |
Register.Id and Register.Name |
register.id and register.name |
The lane, till, or station. |
Each one is null when the transaction was created without it, and Register is null when the transaction has no register. Transaction.PartiallyApproved carries the same four fields.
To attribute an event to a terminal, read these fields from the event body. You don't need to read the transaction back. Read it with GET /api/transactions/{transactionId} only when you need something the event doesn't carry. See Webhook integration for the envelope, the signature, and retry behavior.
Receipts
The terminal owns the point-of-sale receipt. Print it from the create response the way your device prints any receipt: WinkPG doesn't send anything to the terminal's printer.
WinkPG also keeps its own receipt for the transaction, and you can read it over the API to print or reprint it. Read it as JSON to lay out your own printout, or as an HTML document ready to print. The receipt is generated in the background shortly after the sale, so a terminal that prints straight away asks for it with POST /api/transactions/by-merchant/{merchantId}/{transactionId}/receipts and receiptType set to Authorization, which returns the receipt and generates it first if it doesn't exist yet. Either way, the receipt shows the card's brand and last four digits only. See Transaction receipts over the API for the requests.
Refunds, voids, and reversals
A card-present sale is corrected through the same operations as any other transaction, against its transaction identifier. Nothing about the follow-up depends on the terminal. See Refunds, voids, and reversals for which operation applies at which point in the transaction's life.
What to log, and what not to
Never log track data, chip data, the PIN, or any cryptogram, in cleartext or encrypted form. They're card data, and the rules that govern a card number govern them too.
Log the transaction identifier, the entry mode, and the device serial number. Together they answer most support questions about a terminal sale: which transaction it was, how the card was read, and which device read it.
See also
- Transaction custom fields for defining a field before a terminal sends it.
- Webhook integration for receiving and verifying the lifecycle events a terminal sale produces.
- Transaction receipts over the API for printing or reprinting the gateway's receipt for a terminal sale.
- Refunds, voids, and reversals for correcting a terminal sale after the fact.
- Reusing a stored payment method with payment tokens for charging a card stored from a terminal read.
- Quickstart: Direct API for the base create request this guide adds to.
- Transaction lifecycle and settlement for the stages a terminal sale moves through and when its batch closes.