Introduction
Tap2ID allows loyalty members to identify themselves at the point of sale by simply tapping their payment card on the terminal — no phone number, no app, no barcode needed. The POS reads the card, matches it to a Diller loyalty member, fetches their coupons and benefits, applies discounts to the basket, and then charges the final amount — all in a seamless checkout experience.
This guide covers how to integrate Tap2ID into your POS system using two terminal families:
Terminal | SDK | Card Identification Method |
Nets (Ingenico/Baxi) | BAXI.NET SDK ( | SendJson with DAM/Storebox |
Verifone P630 | PaymentSdk (PSDK) | Card Acquisition with tokenization |
Both follow the same high-level flow but use different SDK calls to obtain the card identifier.
How Tap2ID Works
The checkout flow follows four steps:
Card read (no charge) — The terminal reads the card and returns a token or member identifier. Nets uses SendJson → member ID from Storebox. Verifone uses Card Acquisition → card token.
Diller lookup — The POS sends the identifier to the Diller API, which returns member details and available coupons.
Apply benefits — The POS applies coupons and discounts to the basket and calculates the final amount (e.g. 349 NOK → 299 NOK after discount).
Payment (card charged) — The terminal processes a standard payment for the discounted total. The customer taps or inserts their card.
The member must have pre-registered their card in Diller. If the card is not recognized, the POS can fall back to phone number lookup and then register the card for future Tap2ID use.
Tap2ID with Nets (Baxi.NET)
Prerequisites
BAXI.NET SDK (
baxi_dotnet.dllv1.14.1 or later) — available from NexiGroupTerminal with Storebox/DAM support enabled (contact Nets/NexiGroup)
PreventLoyaltyFromPurchaseproperty set to0on the BaxiCtrl instanceUseExtendedLocalModeproperty set to1Diller API credentials (
client_id,client_secret,store_id)
Step 1: Read Card & Identify Member
At the start of checkout, send a SendJson call to the terminal with a GetAsset (action 193) request. This prompts the customer to tap their card. The terminal reads the card and queries Storebox for a linked member ID — without charging the card.
SendJson Request:
{ "da": { "ver": "1.0", "type": 101, "action": 193, "ra2t": { "ver": "1.0", "query": [ { "mode": 4, "amount": "34900", "txntype": "30" } ] }, "ra2dam": { "ver": "2.0", "requestid": "20260213143022-a1b2", "getasset": { "asset": { "template": { "name": "storebox" }, "keys": [ { "cardref": "<cardref.001>" } ] } } } } }Field | Description |
| Asset query message type |
| GetAsset — read card and fetch linked data |
| Transaction + asset lookup mode |
| The basket total in øre (cents). 349.00 NOK = |
| Indicates a purchase context |
| A unique request identifier you generate (e.g. timestamp + random) |
| Target the Storebox DAM system |
| Literal placeholder — the terminal replaces this with the actual card reference after tap |
Important: The <cardref.001> value must be sent as a literal string including the angle brackets. Ensure your JSON serializer does not escape the < and > characters.
Calling SendJson in code:
var args = new SendJsonEventArgs { JsonString = jsonPayload // The JSON above as a string }; _baxi.SendJson(args);Response — OnLocalMode / OnJsonReceived event:
{ "da": { "ver": "1.0", "type": 101, "action": 193, "status": "5:0", "statustext": "OK", "dam2ra": { "requestid": "20260213143022-a1b2", "responseid": "6b9e963e-eec2-4615-a20f-f47d60185c8e", "ver": "2.0", "getasset": { "asset": { "template": "92debb63-0cc5-4570-aeaf-a3f9fb6c0563", "keys": [ { "cardref": "57fb361c-75b6-11eb-a45e-5c4242535400" } ], "attributes": [ { "payload": "{\"id\":\"800unxobgy...\",\"posapi\":{\"userId\":[{\"type\":\"pan\",\"value\":\"57fb361c-75b6-11eb-a45e-5c4242535400\"}]},\"cardlinkapi\":{...}}" } ] } } } } }Extracting the member identifier:
da.dam2ra.getasset.asset.keys[0].cardref→ the card reference UUIDda.dam2ra.getasset.asset.attributes[0].payload→ a JSON string. Parse it to extractmemberIdorloyaltyapi.memberIdandposapi.userId[0].value
Use the member ID to query the Diller API in the next step.
Step 2: Query Diller for Member Details
GET https://api.dillerapp.com/api/v2.0/stores/{storeId}/members/search?cardidentifier={memberId} Authorization: Bearer {access_token}If the member is found, fetch their coupons:
GET https://api.dillerapp.com/api/v2.0/stores/{storeId}/members/{memberId}/coupons/ Authorization: Bearer {access_token}Display the available coupons and discounts to the cashier, let them select which to apply, and recalculate the basket total.
Step 3: Process Payment
var args = new TransferAmountEventArgs { OperID = "0000", Type1 = 0x30, // Purchase Amount1 = 29900, // 299 NOK (in øre) — the discounted total Type2 = 0x30, Amount2 = 0, Type3 = 0x30, Amount3 = 0, HostData = "", ArticleDetails = "", PaymentConditionCode = "", AuthCode = "" }; _baxi.TransferAmount(args); // Wait for OnLocalMode event — customer taps/inserts card and enters PINField | Description |
| Purchase transaction |
| Refund transaction |
| Amount in øre (cents). 299.00 NOK = |
After successful payment, sync the transaction to Diller (see Create Transaction below).
Registering a New Card (Nets)
If the Storebox lookup returns no member data, ask the customer for their phone number, search Diller by phone, then link the card using InsertAsset (action 191):
{ "da": { "ver": "1.0", "type": 101, "action": 191, "ra2t": { "ver": "1.0", "query": [ { "mode": 4 } ] }, "ra2dam": { "ver": "2.0", "requestid": "20260213144500-c3d4", "insertasset": { "asset": { "template": { "name": "storebox" }, "keys": [ { "cardref": "<cardref.001>" } ], "attributes": [ { "msisdn": "4712345678" }, { "memberno": "m_abc123def" } ] } } } } }Field | Description |
| InsertAsset — associate card with member |
| Member's phone number with country code (e.g. |
| Diller member ID (from the phone search result) |
| Terminal replaces with the tapped card's reference |
Check statustext === "OK" to confirm the card was registered successfully.
Tap2ID with Verifone
Prerequisites
Verifone PaymentSdk (PSDK v3.68.7 or later) —
PaymentSdk-x64-net5.0.dllVerifone P630 terminal (or compatible model)
Tokenization enabled on the Verifone backend for your terminal TID (contact Verifone support)
Terminal connection via TCP/IP or serial
Diller API credentials (
client_id,client_secret,store_id)
Step 1: Card Acquisition
The SDK initialization sequence is:
Initialize SDK →
PaymentSdk.InitializeFromValues(listener, params)Login →
TransactionManager.LoginWithCredentials(credentials)Start Session →
TransactionManager.StartSession(transaction)Card Acquisition →
TransactionManager.RequestCardData2(request)
var request = CardAcquisitionRequest.Create( tokenRequest: true, // IMPORTANT: Request tokenization message: "Please tap card", presentationMethods: new[] { CardPresentationMethod.CTLS_CARD, CardPresentationMethod.CHIP, CardPresentationMethod.MAG_STRIPE } ); _transactionManager.RequestCardData2(request); // Wait for CardInformationReceivedEvent callback (up to 60 seconds)Important: tokenRequest: true is essential. Tokenization must also be enabled on the backend through Verifone support.
Response — CardInformationReceivedEvent:
public override void HandleCardInformationReceivedEvent(CardInformationReceivedEvent evt) { var cardInfo = evt.CardInformation; string brand = cardInfo.PaymentBrand; string panLast4 = cardInfo.PanLast4; string expiry = cardInfo.CardExpiry; var tokens = cardInfo.Tokens; foreach (var token in tokens) { string type = token.TokenType; string value = token.Value; string scheme = token.Scheme; } string panHandle = cardInfo.PanHandle; }Token Type | Description | Use for Diller? |
| Persistent card identifier generated by Verifone backend | Yes — preferred |
| Token for reusing the card in subsequent transactions | Fallback if ANALYTICS not available |
Use the ANALYTICS token (or REUSE as fallback) as the card identifier for Diller member lookup.
Step 2: Query Diller for Member Details
GET https://api.dillerapp.com/api/v2.0/stores/{storeId}/members/search?cardidentifier={analyticsToken} Authorization: Bearer {access_token}If the member is found, fetch their coupons:
GET https://api.dillerapp.com/api/v2.0/stores/{storeId}/members/{memberId}/coupons/ Authorization: Bearer {access_token}
Step 3: Process Payment
var payment = Payment.Create(); var amount = VerifoneSdk.Decimal.Create(29900); // 299.00 NOK payment.AmountTotals.Subtotal = amount; payment.AmountTotals.Total = amount; payment.Currency = "578"; // ISO 4217 numeric payment.TransactionType = TransactionType.PAYMENT; payment.Invoice = "INV-2026-001"; payment.RequestedCardPresentationMethods = new[] { CardPresentationMethod.CTLS_CARD, CardPresentationMethod.CHIP, CardPresentationMethod.MAG_STRIPE }; _transactionManager.StartPayment(payment); // Wait for PaymentCompletedEvent callback (up to 90 seconds for PIN entry)public override void HandlePaymentCompletedEvent(PaymentCompletedEvent evt) { var result = evt.Payment.AuthorizationResult; var txnId = evt.Payment.TransactionId; var auth = evt.Payment.AuthCode; }Currency | ISO 4217 Numeric |
NOK |
|
SEK |
|
EUR |
|
DKK |
|
GBP |
|
After successful payment, sync the transaction to Diller (see Create Transaction below).
Registering a New Card (Verifone)
If the Diller API returns no member for the token, ask for the customer's phone number, search by phone, then register the card:
POST https://api.dillerapp.com/api/v2.0/stores/{storeId}/members/{memberId}/register-card Authorization: Bearer {access_token} Content-Type: application/json { "identifier": "tok_analytics_xxxx...", "brand": "VISA", "expiry_date": "26-12" }Field | Description |
| The ANALYTICS token value from card acquisition |
| Normalized card brand, uppercase: |
| Expiry in |
Note: Terminal expiry formats vary. You may receive YYMM, MMYY, or YY/MM. Normalize to YY-MM before sending to Diller.
Diller API Reference
Authentication
Diller uses OAuth2 Client Credentials flow. Obtain an access token before making API calls:
POST https://api.dillerapp.com/connect/token Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&scope=api.retailer&client_id={your_client_id}&client_secret={your_client_secret}Response:
{ "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "scope": "api.retailer" }Cache the token and refresh when expired. Use it in all subsequent requests as Authorization: Bearer {access_token}.
Environment | Base URL |
Production | |
Pre-release / Testing |
Identify Member by Card Token
GET /api/v2.0/stores/{storeId}/members/search?cardidentifier={token}Parameter | Type | Description |
| path | Your Diller store ID |
| query | The token from card acquisition |
Response (member found):
[ { "id": "m_nsr714RRBEHX", "first_name": "Ola", "last_name": "Nordmann", "phone": { "country_code": "+47", "number": "12345678" }, "email": "ola@example.com", "stamps": { "current": 5, "needed": 10 }, "points": { "current": 250 }, "membership_level": "Gold" } ]Response (no member found): [] — card is not registered. Proceed to phone number search or enrollment.
Search Member by Phone
GET /api/v2.0/stores/{storeId}/members/search?phone={phoneNumber}
Parameter | Type | Description |
| query | Phone number (e.g. |
Get Member Coupons
GET /api/v2.0/stores/{storeId}/members/{memberId}/coupons/Response:
[ { "id": "coupon_abc123", "name": "20% off everything", "type": "percentage", "value": 20, "min_purchase_amount": 100, "expires_at": "2026-03-01T00:00:00Z", "is_redeemable": true } ]
Register Card to Member
POST /api/v2.0/stores/{storeId}/members/{memberId}/register-card Content-Type: application/json { "identifier": "tok_analytics_xxxx...", "brand": "VISA", "expiry_date": "26-12" }Field | Type | Description |
| string | Card token (ANALYTICS token from Verifone) |
| string | Card brand, uppercase: |
| string | Card expiry in |
Returns 200 OK on success.
Enroll New Member
POST /api/v2.0/stores/{storeId}/members/enroll Content-Type: application/json { "phone": { "country_code": "+47", "number": "12345678" }, "department_id": "101", "consent": true, "origin": { "system_id": "your-pos-system", "employee_id": "1", "department_id": "101", "channel": "pos" }, "additional_info": { "first_name": "Ola", "last_name": "Nordmann", "email": "ola@example.com" } }Returns 201 Created with the new member object (includes id). After enrollment, register the customer's card for future Tap2ID recognition.
Create Transaction
After a successful payment with a loyalty member, sync the transaction to Diller so points and stamps are awarded:
POST /api/v2.0/stores/{storeId}/members/{memberId}/transactions Content-Type: application/json { "external_id": "ORDER-12345", "total": 299.00, "currency": "NOK", "created_at": "2026-02-13T14:30:00Z", "origin": { "system_id": "your-pos-system", "employee_id": "1", "department_id": "101", "channel": "pos" }, "details": [ { "product_id": "SKU-001", "product_name": "Example Product", "quantity": 1, "unit_price": 349.00, "total": 349.00 } ], "coupons": [ { "coupon_id": "coupon_abc123", "discount_amount": 50.00 } ] }
Key Differences: Nets vs Verifone
Aspect | Nets (Baxi) | Verifone |
SDK | BAXI.NET ( | PaymentSdk (PSDK) — .NET library |
Card Read |
|
|
Identifier Type | Storebox member ID | ANALYTICS token (cryptographic, server-generated) |
Diller Lookup |
|
|
Card Registration | Terminal-side: InsertAsset (action 191) | API-side: |
Initialization |
| Initialize → Login → Start Session → ready |
Session Management | Not required (stateless) | Required (explicit session start/end) |
Amount Format | String in øre for SendJson, integer in øre for TransferAmount | Integer in minor units (øre/cents) |
Currency | Configured on terminal | Passed per-request as ISO 4217 numeric code |
OS Requirement | Windows only (COM/WinForms) | Windows or Linux |
Threading | STA thread required (COM) | Standard async |
Payment Call |
|
|
Refund Call |
|
|
Complete Flow Diagrams
Nets (Baxi) — Card Recognized
POS sends SendJson (GetAsset 193) to terminal
Customer taps card → terminal queries Storebox → returns memberId and cardRef to POS
POS calls
GET /members/search?cardidentifier={memberId}→ Diller returns member dataPOS calls
GET /members/{id}/coupons→ Diller returns available couponsPOS applies discounts and recalculates total
POS sends TransferAmount (0x30, discounted amount in øre) → customer taps and enters PIN → terminal returns success and authCode
POS calls
POST /members/{id}/transactionsto sync to Diller
Verifone — Card Recognized
POS initializes SDK and logs in, starts session
POS sends RequestCardData2 (tokenRequest=true) → customer taps card → Verifone Cloud returns ANALYTICS token → terminal returns CardInformation (token, brand, last4, expiry) to POS
POS calls
GET /members/search?cardidentifier={analyticsToken}→ Diller returns member dataPOS calls
GET /members/{id}/coupons→ Diller returns available couponsPOS applies discounts and recalculates total
POS sends StartPayment (discounted amount, currency) → customer taps and enters PIN → terminal returns PaymentCompleted (txnId, authCode)
POS ends session
POS calls
POST /members/{id}/transactionsto sync to Diller
Either Terminal — Card Not Recognized (First-Time Registration)
Card read (no charge) → terminal returns token or cardRef
POS calls
GET /members/search?cardidentifier={token}→ Diller returns empty resultCashier asks customer for phone number
POS calls
GET /members/search?phone=12345678→ Diller returns memberRegister card for future Tap2ID:
Nets: POS sends InsertAsset (action 191) to terminal → customer taps → card linked in Storebox
Verifone: POS calls
POST /members/{id}/register-cardwith identifier, brand, and expiry
Continue to coupons and payment as normal
FAQ & Troubleshooting
General
Q: Does the first card read (Tap #1) charge the customer? No. Both the Nets SendJson/GetAsset and Verifone Card Acquisition are read-only operations. The customer is only charged during the explicit payment step.
Q: What if the customer's card is not registered? The POS should fall back to phone number lookup. If a member is found by phone, register the card so that future visits use Tap2ID automatically.
Q: Can I use the same Diller credentials for both terminal types? Yes. Diller credentials are terminal-independent. The only difference is how you obtain the card identifier.
Nets (Baxi) Specific
Q: SendJson returns no member data / empty attributes Storebox/DAM must be configured on the terminal. Contact NexiGroup to enable Storebox with the "storebox" template for your terminal fleet.
Q: MethodRejectCode: 7001 (invalid JSON) Ensure the <cardref.001> angle brackets are not escaped. Use a JSON serializer that preserves literal < and > characters.
Q: MethodRejectCode: 7002 (not ready) The terminal is not in a ready state. Wait for the OnTerminalReady event before sending commands. You may need to reinitialize.
Q: InsertAsset fails with phone number error Ensure the phone number includes the country code prefix (e.g. "4712345678" for Norwegian numbers).
Verifone Specific
Q: Card acquisition returns no tokens Tokenization must be enabled on the Verifone backend for your terminal TID. Contact your Verifone account manager to enable the ANALYTICS token type.
Q: "Terminal not initialized" error The SDK requires the full initialization sequence: Initialize → Login → Start Session. Re-initialize if the terminal was powered off or the connection dropped.
Q: Currency mismatch / wrong amount charged Ensure the currency code is ISO 4217 numeric (e.g. "578" for NOK, not "NOK"). Amounts must be in minor units (øre/cents).
Q: Session already open error End the current session before starting a new one. The SDK only allows one active session at a time.
Diller API
Q: 401 Unauthorized from Diller API Your access token has expired. Re-authenticate using the /connect/token endpoint. Tokens typically expire after 1 hour.
Q: Card registration returns an error Verify the brand is uppercase ("VISA", not "visa") and the expiry format is "YY-MM" (e.g. "26-12", not "2612" or "12/26").
For questions about Diller API access or terminal provisioning, contact support@diller.io.
