PaymentsIn-Person PaymentsAdvanced flowsShopper engagementEnterprise

Shopper engagement

Capture email addresses, loyalty IDs, and survey responses on the terminal screen during the payment flow.

View as MarkdownInstall skills

The terminal screen doesn't have to be blank while waiting for card presentation or after payment completes. Shopper engagement lets you insert custom data-capture prompts into the payment flow — collecting email addresses for digital receipts, loyalty opt-ins, NPS surveys, or marketing consent — without interrupting the payment experience.

Prompt typesAsk

TypeWhat it capturesWhen it appears
emailCustomer email addressAfter payment, before receipt
phoneMobile number for SMS receiptAfter payment, before receipt
loyalty_opt_inLoyalty programme sign-up Y/NAfter payment
text_inputFree-text field (custom label)Before or after payment
single_choiceOne-of-N selection (e.g. NPS 1–5)Before or after payment
multi_choiceCheckbox selectionAfter payment
signatureOn-screen signature captureBefore or after payment

Configure prompts in the APIAsk

Pass an engagementConfig array on the payment create call:

const terminalPayment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 4500,
  currency: 'USD',
  reference: 'order_1042',
  engagementConfig: [
    {
      type: 'email',
      label: 'Get your digital receipt',
      required: false,
      position: 'after_payment',
    },
    {
      type: 'single_choice',
      label: 'How was your visit today?',
      options: ['Excellent', 'Good', 'Average', 'Poor'],
      required: false,
      position: 'after_payment',
    },
    {
      type: 'loyalty_opt_in',
      label: 'Join our loyalty programme and earn 1 point per $1 spent',
      required: false,
      position: 'after_payment',
    },
  ],
});

Prompts appear in the order listed. The customer can skip non-required prompts with a single tap.

Reading engagement responsesAsk

Engagement responses are included on the terminal_payment.completed event:

if (event.type === 'terminal_payment.completed') {
  const tp = event.data.object;

  for (const response of tp.engagementResponses ?? []) {
    switch (response.type) {
      case 'email':
        await crm.upsertContact({ email: response.value, source: 'terminal' });
        break;
      case 'single_choice':
        await analytics.recordNPS({ score: response.value, reference: tp.reference });
        break;
      case 'loyalty_opt_in':
        if (response.value === 'yes') {
          await loyalty.enroll({ reference: tp.reference });
        }
        break;
    }
  }
}

Idle screenAsk

Between transactions, the terminal displays an idle screen. You can customise it with branded content, promotional messages, or QR codes:

await vinr.terminal.terminals.configure({
  id: 'term_01HZ5QXYZ',
  idleScreen: {
    imageUrl: 'https://cdn.example.com/promo-banner.png',
    qrCode: {
      url: 'https://example.com/app-download',
      label: 'Download our app',
    },
    inactivityTimeout: 30, // seconds before returning to idle after a session
  },
});

Idle screen images must be PNG or JPEG, maximum 1920×1080px, under 500KB.

Signature captureAsk

For transactions requiring a written signature (certain card-not-present flows, high-value transactions, or age-verification), enable signature capture:

const terminalPayment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 150000,
  currency: 'USD',
  reference: 'order_highvalue',
  engagementConfig: [
    {
      type: 'signature',
      label: 'Please sign to confirm your purchase',
      position: 'after_payment',
      required: true,
    },
  ],
});

The signature image is returned as a base64-encoded PNG in engagementResponses[].signatureImage. Store it in your records — it may be required for chargeback disputes.

Display limitsAsk

ConstraintLimit
Max prompts per payment5
Max single_choice options6
Max multi_choice options8
text_input max characters80
Idle screen image size1920×1080px max, 500KB max
Idle screen formatsPNG, JPEG
Was this page helpful?
Edit on GitHub

Last updated on

On this page