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.