If you're building an e-commerce site or SaaS product targeting Turkey, you'll eventually need to accept payments. iyzico is a Turkey-based payment infrastructure with credit card, 3D Secure, and bank transfer support that integrates cleanly into a Node.js/Express backend. This guide walks through a real integration — from opening a sandbox account to verifying webhooks in production.
Related reading: REST API security guide · OAuth 2.0 and OIDC · API rate limiting strategies
What Is iyzico?
iyzico is a Turkey-based payment service provider (PSP). It offers credit/debit card payments, 3D Secure, BKM Express, bank transfer, installments, and marketplace sub-merchant payouts through a single API. Official SDKs exist for PHP, Node.js, Python, Java and .NET, plus plugins for Shopify, WooCommerce, Magento and similar platforms.
Getting Your API Key and Secret Key
After registering on the iyzico Merchant Panel, go to Settings > API Integration to get a separate apiKey/secretKey pair for sandbox and for production. The two environments are fully isolated — a sandbox key cannot process a live transaction, and that's intentional.
.env and add that file to .gitignore — it's the one piece of secret material every signed iyzico request depends on.Developing Against the Sandbox
All development and testing happens against the sandbox endpoint (https://sandbox-api.iyzipay.com); real cards are not accepted there. iyzico provides dozens of test cards that simulate successful payments, foreign cards, and specific failure scenarios — so you can exercise both the happy path and error handling before going live.
Node.js Setup
npm install iyzipay
Install the official Node.js SDK
Example: npm install iyzipay --save
const Iyzipay = require('iyzipay');
const iyzipay = new Iyzipay({
apiKey: process.env.IYZICO_API_KEY,
secretKey: process.env.IYZICO_SECRET_KEY,
uri: process.env.IYZICO_BASE_URL // sandbox: https://sandbox-api.iyzipay.com
});
Integrating the Checkout Form
The fastest path — and the one that minimizes your PCI-DSS scope — is the Checkout Form: card data never touches your server, customers are sent to a form hosted by iyzico. Your server only generates a token and redirects the user to that form.
const request = {
locale: Iyzipay.LOCALE.TR,
conversationId: orderId, // your own order ID
price: '100.0', // basket total
paidPrice: '100.0', // amount actually charged (after discounts)
currency: Iyzipay.CURRENCY.TRY,
basketId: 'B' + orderId,
paymentGroup: Iyzipay.PAYMENT_GROUP.PRODUCT,
callbackUrl: 'https://example.com/payment/callback',
buyer: {
id: String(user.id),
name: user.firstName,
surname: user.lastName,
gsmNumber: user.phone,
email: user.email,
identityNumber: user.nationalId, // required field
ip: req.ip,
city: user.city,
country: 'Turkey'
},
basketItems: cart.items.map(i => ({
id: String(i.id),
name: i.name,
category1: i.category,
itemType: Iyzipay.BASKET_ITEM_TYPE.PHYSICAL,
price: String(i.price)
}))
};
iyzipay.checkoutFormInitialize.create(request, (err, result) => {
if (err || result.status !== 'success') return res.status(400).json({ error: result?.errorMessage });
res.json({ checkoutFormContent: result.checkoutFormContent, token: result.token });
});
The returned checkoutFormContent is a <script> block you render into an empty <div> on your page. Once the customer completes payment, iyzico redirects them to your callbackUrl; there you look up the result using the token:
iyzipay.checkoutForm.retrieve({
locale: Iyzipay.LOCALE.TR,
conversationId: orderId,
token: req.body.token
}, (err, result) => {
if (result.paymentStatus === 'SUCCESS') {
// mark order as paid, generate invoice
}
});
The 3D Secure Flow
With the Checkout Form, 3D Secure is handled automatically by iyzico. If you build your own card form (direct API payment) instead, you implement a two-step flow: call threeds.initialize to send the user to the issuing bank's verification page, and after the user confirms via SMS/password, the bank redirects back to your callbackUrl where you complete the payment with threeds.create. For most new integrations, letting the Checkout Form manage this complexity is the pragmatic choice.
Receiving Payment Notifications via Webhook
If the customer closes the browser or the callback request never reaches your server, a webhook is what keeps you from missing the payment result. iyzico sends an HTTP POST to your server 10-15 seconds after the first payment attempt, and retries every 15 minutes (up to 3 times) until your server responds with a 2xx.
| Field | Description |
|---|---|
| paymentId | ID of the related payment |
| status | SUCCESS, FAILURE, INIT_THREEDS, CALLBACK_THREEDS, etc. |
| iyziEventType | Request type (PAYMENT_API, THREE_DS_AUTH, BALANCE…) |
| iyziReferenceCode | Unique iyzico reference generated for the request |
X-IYZ-SIGNATURE-V3 header is an HMAC-SHA256 hash of secretKey + iyziEventType + paymentId + paymentConversationId + status; recompute it server-side and compare before acting on the notification.Sandbox Test Cards
Real cards don't work in sandbox; iyzico provides three categories of test cards — successful, foreign, and intentionally failing. A few examples:
| Card Number | Scenario |
|---|---|
| 5528790000000008 | Mastercard credit — successful payment |
| 4543590000000006 | Visa credit — successful payment |
| 4111111111111129 | Insufficient funds (NOT_SUFFICIENT_FUNDS) |
| 4125111111111115 | Expired card (EXPIRED_CARD) |
| 4121111111111119 | Fraud suspect (FRAUD_SUSPECT) |
Common Error Codes
On failure, iyzico returns an errorCode, errorGroup, and a human-readable errorMessage. The ones you'll hit most often:
| Code | Error Group | Meaning |
|---|---|---|
| 10051 | NOT_SUFFICIENT_FUNDS | Card limit or balance is insufficient |
| 10054 | EXPIRED_CARD | The card's expiry date has passed |
| 10084 | INVALID_CVC2 | CVV is incorrect or invalid |
| 1000 | Invalid signature | Your computed hash doesn't match iyzico's |
| 1006 | api key is required | apiKey is missing or wrong in the request |
Security and Best Practices
- Always compute the amount server-side — base the basket total on your database's real prices, not a value sent from the client
- Map conversationId to your own order ID — it's the only reliable way to reconcile a webhook or callback with the right order
- Never store raw card numbers in your own database — use iyzico's card storage/tokenization service if you need saved cards
- Be idempotent when handling both callback and webhook for the same payment — dedupe on paymentId
- Keep secretKey in environment variables, and clearly separate sandbox and production keys (e.g.
.env/.env.production)
Frequently Asked Questions
Do I need a backend server for iyzico, or is frontend-only enough?
A server-side backend is required. The secretKey must never reach the browser; initiating payment, retrieving results, and verifying webhooks all have to happen on your server.
Should I use the Checkout Form or direct API integration?
The Checkout Form is recommended for most projects: card data never touches your server, which significantly shrinks your PCI-DSS scope, and it manages the 3D Secure flow for you. Direct API integration should be reserved for advanced cases that require a fully custom payment UI.
Is the callbackUrl alone enough, without a webhook?
No, it isn't reliable on its own. A customer might close the browser after payment, lose connectivity, or the redirect could fail for other reasons. The webhook is the one mechanism that guarantees the payment result reaches your server even in those cases.
Get professional support for iyzico or any other payment provider integration — from API design to webhook security. Get a Quote