Bonaventure OgetoBy Bonaventure Ogeto|

Paystack Terminal: What Developers Need to Know

Paystack Terminal is a physical payment device you control via API. You send a payment request to the Terminal API with an amount and terminal ID. The device displays the amount and prompts the customer to tap, insert, or swipe their card. Once the payment completes, Paystack sends you a webhook with the transaction result. Your backend processes the webhook the same way it handles any Paystack transaction event.

What Paystack Terminal Is (and Is Not)

Paystack Terminal is a physical point-of-sale device that connects to your Paystack account. It accepts card payments in person: contactless tap (NFC), chip-and-PIN insert, and magnetic stripe swipe.

What it is: A hardware device with a screen, card reader, and network connection. It runs software that communicates with Paystack's servers. You control it through the Terminal API, pushing payment requests from your application.

What it is not: It is not a standalone cash register. It does not manage inventory, print itemised receipts, or handle refunds on the device itself. Those features come from your application. The terminal is the payment acceptance layer. Your application is the business logic layer.

How it differs from online payments: With online payments, the customer types their card details into a web form. With Terminal, the customer presents their physical card to the device. The security model is different (PCI requirements shift to the device and Paystack), but the result is the same: a Paystack transaction you can verify and track through the API.

For the full overview of in-person payments with Paystack, see the complete Terminal guide.

Terminal Architecture: How the Pieces Fit

The Terminal system has four components that communicate in a specific order.

1. Your application (backend). This is where the transaction starts. Your point-of-sale app, order management system, or any backend service creates a payment request with an amount and sends it to the Paystack Terminal API.

2. Paystack Terminal API. Paystack receives your payment request and pushes it to the physical terminal device. The API is at https://api.paystack.co/terminal.

3. The physical terminal. The device displays the amount, prompts the customer to present their card, processes the card interaction, and sends the result back to Paystack.

4. Paystack webhooks. After the transaction completes (or fails), Paystack sends a webhook to your server with the transaction details. You process this webhook the same way you process any Paystack transaction event.

The flow:

  1. Your app: POST to /terminal/:terminal_id/event with payment details.
  2. Paystack pushes the request to the device.
  3. Device: shows amount, customer taps/inserts card.
  4. Device: sends card data securely to Paystack for processing.
  5. Paystack: processes the charge, sends webhook to your server.
  6. Your app: receives webhook, updates order status.

Registering a Terminal

Before you can send payment requests to a terminal, it must be registered on your Paystack account.

Physical registration: When you receive a Paystack Terminal device, you register it through the Paystack dashboard. The device has a serial number. You enter the serial number in the dashboard, and Paystack assigns a terminal ID to it.

Listing your terminals: Use the API to get a list of all terminals on your account.

// list-terminals.js
const axios = require('axios');

async function listTerminals() {
  var response = await axios.get(
    'https://api.paystack.co/terminal',
    {
      headers: {
        Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
      },
    }
  );

  return response.data.data;
}

// Returns an array of terminal objects with id, serial_number, device_make, status, etc.

Terminal status: A terminal can be "active" (ready to accept payments) or "inactive" (not connected or not configured). Check the status before sending payment requests.

Multiple terminals: A single Paystack account can have multiple terminals. Each has its own terminal ID. Your application routes payment requests to the correct terminal based on location, register number, or operator assignment.

Sending a Payment Request to a Terminal

To collect a payment, you send an event to the terminal with the payment details.

// push-payment.js
const axios = require('axios');

async function pushPaymentToTerminal(terminalId, amount, reference) {
  var response = await axios.post(
    'https://api.paystack.co/terminal/' + terminalId + '/event',
    {
      type: 'invoice',
      action: 'process',
      data: {
        id: reference,
        amount: amount, // in kobo
      },
    },
    {
      headers: {
        Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY,
        'Content-Type': 'application/json',
      },
    }
  );

  return response.data;
}

// Usage: Push a 5,000 NGN payment to terminal
var result = await pushPaymentToTerminal(
  'TERMINAL_ID',
  500000,  // 5000 NGN in kobo
  'ORDER-' + Date.now()
);

Amount is in kobo. Like all Paystack amounts, terminal payments use the smallest currency unit. 500000 kobo = 5,000 NGN.

Reference is your order ID. Use a unique reference for each transaction so you can match the webhook response to the correct order in your system.

The terminal displays the amount immediately. Once the API call succeeds, the device shows the amount on its screen and waits for the customer to present their card. There is no delay between your API call and the terminal display.

Receiving Transaction Results

The transaction result comes through your standard Paystack webhook endpoint. If you already handle charge.success webhooks for online payments, the same handler works for terminal payments.

// terminal-webhook.js
var crypto = require('crypto');

function handleTerminalWebhook(req, res) {
  // Verify signature (same as any Paystack webhook)
  var hash = crypto
    .createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (hash !== req.headers['x-paystack-signature']) {
    return res.status(401).send('Invalid signature');
  }

  var event = req.body;

  if (event.event === 'charge.success') {
    var transaction = event.data;
    var reference = transaction.reference;
    var amount = transaction.amount;
    var channel = transaction.channel; // "card" for terminal payments

    // Update your order
    console.log('Payment received: ' + reference + ' Amount: ' + amount + ' Channel: ' + channel);

    // Mark the order as paid in your database
  }

  return res.status(200).send('OK');
}

The channel field tells you how the payment was made. For terminal payments, the channel is typically "card". For online payments, it might be "card", "bank", or "mobile_money". You can use this field to distinguish terminal transactions from online ones in your reporting.

Terminal-specific events. In addition to standard charge events, Paystack may send terminal-specific events for device status changes, connection issues, or payment request timeouts. Handle these to keep your system in sync with the device state.

Hardware Considerations

The physical terminal device has characteristics that affect your application design.

Connectivity. Terminals need an internet connection. They typically connect via Wi-Fi or mobile data (SIM card). If the connection drops during a transaction, the device will attempt to reconnect and complete the transaction. Your application should handle delayed webhook notifications.

Power. Terminals have batteries for portability but need charging. If a terminal dies mid-transaction, the payment does not go through. Your application should handle the case where a payment request was sent but no webhook arrives (timeout scenario).

Screen limitations. The terminal screen is small. Custom messages or prompts sent to the terminal should be short and clear. Do not send paragraphs of text.

Card types supported. Terminals typically support Visa, Mastercard, and Verve cards through contactless (NFC), chip-and-PIN, and magnetic stripe. The exact card types depend on your market and terminal model.

Physical security. Terminals store sensitive card-processing firmware. If a device is lost or stolen, deactivate it immediately through the Paystack dashboard. Paystack handles the PCI compliance for the device itself.

Testing Terminal Integrations

You do not need a physical terminal to start developing. Paystack provides ways to test terminal integrations in development.

Virtual terminals. In test mode, you can create virtual terminals that simulate the behaviour of physical devices. Push payment requests to them and receive simulated webhook responses.

Test mode limitations. Test mode simulates the API flow but not the physical card interaction. You cannot test NFC tap behaviour, chip-and-PIN prompts, or receipt printing in test mode. These require a physical device.

Staging with real devices. When you move to integration testing with physical terminals, use test keys and test cards. Paystack test cards work on physical terminals in test mode just as they work online.

For comprehensive testing strategies, see the complete testing guide.

Common Pitfalls

Ignoring timeouts. A customer might walk away without presenting their card. Your app should handle the case where a payment request was sent but no result comes back. Implement a timeout (e.g., 5 minutes) after which you cancel the pending payment and reset the terminal.

Not matching references. Every payment request should have a unique reference that maps to an order in your system. Without this, you cannot match webhooks to orders.

Assuming instant processing. Unlike online payments where the customer is actively completing a form, terminal payments depend on physical actions. The customer might need to find their card, remember their PIN, or try a different card. Build your UX to handle a few minutes of waiting.

Skipping webhook verification. Terminal webhooks must be verified with your secret key just like online webhooks. Do not skip signature verification because "it is coming from a terminal."

Not handling device offline scenarios. If the terminal is offline when you send a payment request, the API call may succeed (Paystack queues the event) but the payment will not process until the device reconnects. Handle queued states in your application.

Key Takeaways

  • Paystack Terminal is a hardware device controlled through the Paystack Terminal API. You push payment requests to it from your server.
  • The flow is: your app creates a payment request, sends it to the Terminal API, the device prompts the customer, and the result comes back via webhook.
  • Each terminal has a unique terminal ID. You must register the terminal on your Paystack dashboard before using it via API.
  • Terminal supports card payments (tap, insert, swipe) and may support other methods depending on your market.
  • You receive the same charge.success and charge.failed webhooks as online payments. Your existing webhook handler works with terminal transactions.
  • Test mode works with virtual terminals or test devices. You do not need physical hardware to start building.

Frequently Asked Questions

Do I need a different Paystack account for Terminal?
No. Terminal uses the same Paystack account and API keys as your online payments. The same secret key works for both. Transactions from terminals and online payments all appear in the same dashboard.
Can I use Terminal for mobile money payments?
Terminal is primarily designed for card payments (tap, insert, swipe). Mobile money is typically handled through online channels. Check the current Paystack documentation for the latest supported payment methods on Terminal devices in your market.
How do I handle refunds for terminal transactions?
Refunds for terminal transactions work the same way as online refunds. Use the Paystack Refund API with the transaction reference. The refund is processed back to the customer's card. You cannot process refunds on the terminal device itself.
What happens if the terminal loses internet during a transaction?
If the connection drops after the card has been read but before processing completes, the terminal will attempt to reconnect and complete the transaction. If it cannot reconnect, the transaction does not go through and the customer's card is not charged. Your app should handle delayed or missing webhooks gracefully.
Can multiple applications share one terminal?
A terminal is linked to one Paystack account. All applications using the same Paystack secret key can send payment requests to the same terminal. However, you should coordinate to avoid sending conflicting requests to the same device simultaneously.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon