Integrating UAE Payment Gateways into Your App: A Practical Guide to Telr and PayTabs
If you're building a commercial app for the UAE market, you will eventually hit the payment gateway question.
Stripe works globally but isn't the default choice here. The UAE has its own dominant gateways — Telr and PayTabs are the two you'll encounter most — and while both have documentation, the docs leave a lot of gaps that only show up when you're actually integrating.
This is the guide I wish existed before we built our first payment flow for a UAE client.
Why not just use Stripe?
Stripe does operate in the UAE, but there are real reasons local gateways get chosen instead:
- Local acquiring banks — Telr and PayTabs process through UAE banks, which means lower decline rates on local cards and faster settlement
- Network International and Mashreq support — common UAE card networks that local gateways handle natively
- Arabic interface support — important for consumer-facing checkouts
- Compliance familiarity — local gateways understand UAE Central Bank (CBUAE) requirements out of the box
- Client preference — many UAE businesses specifically request a local gateway in their contracts
For most UAE-market apps, you'll be integrating one of these two. Here's how they differ in practice.
Telr vs PayTabs — the practical difference
Telr
- SDKs: iOS, Android, Web
- Hosted payment page: yes
- Direct API (card data): yes, requires PCI compliance
- Recurring billing: yes
- Multi-currency: yes
- Sandbox: yes
- Documentation: patchy — expect to fill gaps yourself
- Best for: enterprise clients who specifically request it, established UAE merchants
PayTabs
- SDKs: iOS, Android, Web, Flutter (official package)
- Hosted payment page: yes
- Direct API (card data): yes, requires PCI compliance
- Recurring billing: yes
- Multi-currency: yes
- Sandbox: yes
- Documentation: better structured, still has gaps
- Best for: Flutter apps, faster integrations, Arabic checkout out of the box
The Flutter SDK is the deciding factor for most mobile projects — PayTabs has one, Telr doesn't. If your client hasn't specified a gateway, start with PayTabs.
Setting up Telr
1. Get your credentials
After creating a merchant account, you'll receive:
-
store_id— your merchant identifier -
authkey— your API authentication key
These go in your server-side environment variables. Never expose them client-side.
2. Create a payment order (server-side)
Telr uses a hosted payment page approach for most integrations. You create an order from your backend and redirect the user to Telr's checkout.
// Node.js example
const axios = require('axios');
async function createTelrOrder({ amount, currency, orderId, customerEmail, returnUrl, cancelUrl }) {
const payload = {
method: 'create',
store: process.env.TELR_STORE_ID,
authkey: process.env.TELR_AUTH_KEY,
order: {
cartid: orderId,
test: process.env.NODE_ENV === 'production' ? '0' : '1', // 1 = test mode
amount: amount.toFixed(2),
currency: currency || 'AED',
description: `Order ${orderId}`,
},
return: {
authorised: returnUrl,
declined: cancelUrl,
cancelled: cancelUrl,
},
customer: {
email: customerEmail,
},
};
const response = await axios.post('https://secure.telr.com/gateway/order.json', payload);
return response.data;
}
The response includes an order.url — redirect the user there to complete payment.
3. Handle the return
Telr redirects back to your authorised or declined URL with an order_ref parameter. You then verify the payment status server-side:
async function verifyTelrPayment(orderRef) {
const payload = {
method: 'check',
store: process.env.TELR_STORE_ID,
authkey: process.env.TELR_AUTH_KEY,
order: {
ref: orderRef,
},
};
const response = await axios.post('https://secure.telr.com/gateway/order.json', payload);
const status = response.data?.order?.status?.code;
// 3 = authorised, -1 = declined, -2 = cancelled
return {
success: status === 3,
status,
data: response.data,
};
}
Important: Always verify server-side. Never trust a client-side redirect parameter to confirm payment.
4. Sandbox gotchas
- Use test card
4111 1111 1111 1111with any future expiry and CVV123 - Set
order.testto1— forgetting this is the most common mistake in sandbox - Telr sandbox and production use the same endpoint — the
testflag controls which environment processes the transaction
Setting up PayTabs
PayTabs has a cleaner API structure and better Flutter support, making it the faster integration for mobile-first apps.
1. Get your credentials
From your PayTabs merchant dashboard:
-
profile_id— your merchant profile -
server_key— for server-side API calls -
client_key— safe to use client-side (in their SDK)
2. Create a payment page (server-side)
// Node.js example
async function createPayTabsPayment({ amount, currency, orderId, customerName, customerEmail, returnUrl, callbackUrl }) {
const payload = {
profile_id: process.env.PAYTABS_PROFILE_ID,
tran_type: 'sale',
tran_class: 'ecom',
cart_id: orderId,
cart_currency: currency || 'AED',
cart_amount: amount,
cart_description: `Order ${orderId}`,
paypage_lang: 'en', // or 'ar' for Arabic checkout
customer_details: {
name: customerName,
email: customerEmail,
},
return: returnUrl,
callback: callbackUrl, // server-to-server notification
};
const response = await axios.post(
'https://secure.paytabs.com/payment/request',
payload,
{
headers: {
authorization: process.env.PAYTABS_SERVER_KEY,
},
}
);
return response.data; // includes redirect_url
}
Redirect the user to redirect_url from the response.
3. Handle the callback (IPN)
PayTabs sends a server-to-server callback to your callback URL when a transaction completes. This is more reliable than relying on the return redirect.
// Express.js callback handler
app.post('/payment/callback', async (req, res) => {
const { tran_ref, payment_result } = req.body;
// Verify the transaction server-side
const verifyResponse = await axios.post(
'https://secure.paytabs.com/payment/query',
{
profile_id: process.env.PAYTABS_PROFILE_ID,
tran_ref,
},
{
headers: { authorization: process.env.PAYTABS_SERVER_KEY },
}
);
const result = verifyResponse.data?.payment_result?.response_status;
if (result === 'A') {
// Payment authorised — update your order
}
res.sendStatus(200); // Always return 200 to acknowledge receipt
});
4. Flutter SDK integration
PayTabs maintains an official Flutter package: flutter_paytabs_bridge on pub.dev.
// pubspec.yaml
dependencies:
flutter_paytabs_bridge: ^2.x.x
import 'package:flutter_paytabs_bridge/PaymentSdkConfigurationDetails.dart';
import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.dart';
void startPayment() {
PaytabsFlutter.startPayment(
configuration: PaymentSDKConfiguration(
profileID: "your_profile_id",
serverKey: "your_server_key", // use client_key here in mobile
clientKey: "your_client_key",
cartID: "order_123",
cartDescription: "Product description",
merchantCountryCode: "AE",
currency: "AED",
amount: 100.0,
screenTitle: "Checkout",
billingDetails: BillingDetails(
name: "Customer Name",
email: "customer@email.com",
countryCode: "AE",
),
),
callback: (event) {
if (event["status"] == "success") {
// handle success
} else if (event["status"] == "error") {
// handle error
}
},
);
}
Things that will catch you out
1. AED is the default but not always the setting
Both gateways support multi-currency but default behaviour varies by account configuration. Confirm your merchant account currency settings before going live — a mismatch causes silent failures in sandbox that only appear in production.
2. 3D Secure is mandatory for UAE-issued cards
UAE banks require 3DS authentication for card-not-present transactions. Both gateways handle this, but your integration needs to account for the additional redirect step in the payment flow. Don't design your UX assuming a single-step checkout.
3. Test with real UAE cards before launch
Sandbox test cards pass through without 3DS. Real UAE-issued cards trigger it. Test your complete flow with an actual card in a staging environment before you go live — the 3DS redirect behaviour can break flows that seemed to work perfectly in sandbox.
4. Callback URLs must be publicly accessible
If you're developing locally, use ngrok or a similar tunnel for your callback URL. PayTabs and Telr can't reach localhost.
5. Idempotency — build it in from the start
Network timeouts can cause duplicate payment attempts. Generate a unique cart_id / order_id per transaction attempt and check for it before processing. Both gateways will process duplicates if you let them.
Which one should you use?
- Building a Flutter app? Start with PayTabs — the official Flutter SDK saves meaningful integration time.
- Enterprise client requiring Telr specifically? Telr is fine; just expect to spend more time on the integration and account setup.
-
Need Arabic checkout? Both support it. PayTabs makes it a single parameter (
paypage_lang: 'ar'). - Need recurring billing? Both support it. Telr's implementation is more documented.
For most new projects in the UAE, PayTabs is the faster path to production.
Resources
- Telr API docs: docs.telr.com
- PayTabs technical portal: docs.paytabs.com
- PayTabs developer hub: dev.paytabs.com
- PayTabs Flutter package: pub.dev/packages/flutter_paytabs_bridge
- PayTabs Postman collection: documenter.getpostman.com
We build mobile and web apps for the UAE market at Daiyra 360 Communications — payment gateway integration is something we set up on almost every project. If something in this guide is outdated or wrong, drop a comment and we'll update it.
Top comments (0)