Services Hosting & Servers Tools Blog Search Company TürkçeTR
Get a Quote

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.

Warning
Never ship the secret key to client-side code or commit it to a repo. Keep it in .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.

FieldDescription
paymentIdID of the related payment
statusSUCCESS, FAILURE, INIT_THREEDS, CALLBACK_THREEDS, etc.
iyziEventTypeRequest type (PAYMENT_API, THREE_DS_AUTH, BALANCE…)
iyziReferenceCodeUnique iyzico reference generated for the request
Warning
Never trust a webhook without verifying it actually came from iyzico. The 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 NumberScenario
5528790000000008Mastercard credit — successful payment
4543590000000006Visa credit — successful payment
4111111111111129Insufficient funds (NOT_SUFFICIENT_FUNDS)
4125111111111115Expired card (EXPIRED_CARD)
4121111111111119Fraud 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:

CodeError GroupMeaning
10051NOT_SUFFICIENT_FUNDSCard limit or balance is insufficient
10054EXPIRED_CARDThe card's expiry date has passed
10084INVALID_CVC2CVV is incorrect or invalid
1000Invalid signatureYour computed hash doesn't match iyzico's
1006api key is requiredapiKey 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.

Payment Integration Support

Get professional support for iyzico or any other payment provider integration — from API design to webhook security. Get a Quote

WhatsApp