WinPay Integration DocsTürkçe →

System workflow — how deposits and withdrawals work

WinPay is a payment method: it collects and pays out on your behalf and reports the result to you. You keep the member's balance; WinPay never knows balances, it only says "this much money came in / went out".

There are two flows. In both, three things happen: you send a request → money moves → you receive a callback.

Deposit

What happens

1. Member clicks "Deposit" on your site and enters an amount
2. Your site sends POST /api/v1/deposits/init to WinPay
3. WinPay returns a payment link (payment_url)
4. Your site redirects the member to that link
5. The member confirms the amount and sees an IBAN, an account name and a QR code
6. The member pays from their bank and clicks "I have paid"
7. The payment is verified
8. WinPay sends your site a signed callback
9. Your site credits the member with the amount in the callback
10. The member sees the money in their account

On your side, step by step

STEP 1: Member submits the form
│  ├─ Create a record in your database: request_id (unique), member, amount
│  └─ Status: "pending"
│
STEP 2: Request to WinPay
│  └─ POST /api/v1/deposits/init  (body: request_id, member_id, amount)
│     Headers: X-API-Key, X-WinPay-Timestamp, X-WinPay-Signature
│     Response: transaction_id, payment_url, expires_at (20 min)
│
STEP 3: Redirect the member
│  └─ Send the browser to payment_url (top-level, not an iframe)
│
STEP 4: Wait
│  └─ Do NOT touch the balance. 201 means "request accepted", not "money arrived".
│
STEP 5: Callback arrives
│  ├─ Verify the signature (callback_secret, over the raw body)
│  ├─ Seen this event_id before? Return 200 and stop
│  ├─ Is request_id one of yours? If not, 400
│  └─ status = approved: balance += approved_amount   (NOT amount)
│     status = rejected / expired: no change
│
STEP 6: Return 200 (AFTER the balance is written)

Timeline

When What
0 s Member clicked "Deposit", your site sent the request, link returned
1 s Member is on the payment page
1–2 min Member paid from their bank and clicked "I have paid"
After verification Callback reached your site, balance credited
20 min A request not approved by then becomes expired

Withdrawal

What happens

1. Member clicks "Withdraw", enters amount and IBAN
2. Your site checks the balance and reserves the amount
3. Your site sends POST /api/v1/withdrawals/init to WinPay
4. WinPay accepts the request (201) — no money has moved yet
5. The transfer is made to the member's IBAN
6. WinPay sends your site a callback
7. status = completed → close the withdrawal; status = rejected → release the reserved amount

On your side, step by step

STEP 1: Member submits the form
│  ├─ Enough balance? If not, show an error and send nothing
│  ├─ Reserve the amount (deduct, keep as "pending")
│  └─ Generate request_id (unique, e.g. wd-20260913-000123)
│
STEP 2: Request to WinPay
│  └─ POST /api/v1/withdrawals/init  (request_id, member_id, amount, iban, account_name)
│     Response: transaction_id, status = pending_assignment
│
STEP 3: Wait — do NOT send a second request
│
STEP 4: Callback arrives
│  ├─ Signature + event_id + request_id checks (same as deposit)
│  ├─ completed → withdrawal done, reservation becomes final
│  └─ rejected  → give the reserved amount back to the member
│
STEP 5: Return 200

Who talks to whom

Your server                  WinPay                      Member's bank
     │                          │                              │
     ├─ POST /deposits/init ──► │                              │
     │ ◄── payment_url ──────── │                              │
     │                          │                              │
     └─ redirect member ──────► │ payment page                 │
                                │ ◄──── member pays ───────────┤
                                │                              │
                                │ payment verified             │
     ◄── callback (signed) ──── │                              │
     │                          │                              │
     ├─ update balance          │                              │
     └─ 200 OK ───────────────► │                              │

Concepts you need

request_id — a unique id you generate per request. Sending the same request again with the same request_id does not create a second transaction; the existing one is returned (safe to retry after a timeout). Same request_id with a different body → 409.

Signature — the X-WinPay-Signature header on every request: HMAC-SHA256(timestamp + "." + body, api_secret). The timestamp must be within ±5 minutes (keep your server clock on NTP).

Callback — a POST WinPay sends to your HTTPS URL. Its X-WinPay-Signature header is HMAC-SHA256(body, callback_secret). You dedupe on the body's event_id.

approved_amount — the member may choose a different amount on the payment page than the one you sent. Always credit the callback's approved_amount, never the original amount.

Statuses — only two mean "money moved": approved for deposits, completed for withdrawals. pending_approval means the member clicked "I have paid"; it does not mean money arrived.

What can go wrong

Situation What happens What you do
Member never pays Request becomes expired after 20 min, callback arrives No balance change; show "expired"
Member pays a different amount Callback carries a different approved_amount Credit that amount
Callback does not reach you WinPay retries with growing intervals (for hours) It keeps coming until you return 2xx; never discard a late one
Same callback twice Network retry If event_id is already stored, do not credit again
Your clock drifted Every request gets 401 Set up NTP
Your IP changed 403 Tell us the new IP
Withdrawal rejected Callback rejected Release the reserved amount
No account fits the amount 503 Show "temporarily unavailable", retry later

Key numbers

Topic Value
Payment link lifetime 20 minutes
Signature time window ±300 seconds
Rate limit (init endpoints) 600 / minute, per API key
Callback timeout 10 seconds (return 2xx within that)
Callback retries Exponential backoff, up to 1 hour apart
Amount format "1000.00" — dot, max 2 decimals, string
IBAN TR, 26 characters

Next step

Developer: Integration guide. Field definitions: API reference.