Integration
One branch. One redirect. One webhook.
Enough of the interface to estimate the work before you talk to us. The full API contract and a sandbox key follow under NDA — this page exists so that step isn’t the one that tells you whether it fits.
One route for Türkiye
Keep your existing payment stack.
When the customer or the transaction belongs to Türkiye, create a Finletry checkout and continue through the local route.
- One checkout endpoint
- One redirect
- Signed webhooks
- A single transaction state across the Türkiye flow
Your engineers integrate the route. Finletry operates what happens behind it.
- DEExisting provider
- FRExisting provider
- GBExisting provider
- USExisting provider
- TRFinletry
Finletry
The Türkiye layer
- checkout_url createdPOST /v1/payment_intents
- signed webhookpayment_intent.succeeded
- no rewrite to your stackSame API. More markets.
What you implement
Three changes in your product.
The interface deliberately mirrors the shape your payment code already has: a bearer key, an Idempotency-Key header on every mutating call, and signed webhooks. The concepts map one to one rather than asking you to learn a second model.
Route Turkish transactions to us
One conditional branch where you create a payment today. Everything else in your checkout stays where it is.
Redirect the buyer to the checkout URL
We host the local payment page. Card data reaches neither you nor us — our scope is PCI-DSS SAQ-A.
Handle one signed webhook
Verify the signature against the raw body, then act on success. Delivery is at-least-once, so stay idempotent on event.id.
Step one
Create a payment intent.
Send the amount, the event and the organizer’s reference on your side. We return a hosted checkout URL and an estimated breakdown. Redirect the buyer; everything after that is ours until the webhook arrives.
POST /v1/payment_intents
Authorization: Bearer fk_test_…
Idempotency-Key: 8f14e45f-ea0b-4f1c-9a3d-2b7c8e1f0a55
{
"amount_minor": 100000,
"currency": "TRY",
"event": {
"external_ref": "evt_platform_9d2h",
"title": "Tech Meetup Istanbul",
"type": "conference",
"starts_at": "2026-09-14T18:00:00+03:00",
"ends_at": "2026-09-14T21:00:00+03:00"
},
"organizer": {
"external_ref": "usr_platform_4kq1",
"email": "organizer@example.com"
},
"buyer": { "email": "buyer@example.com", "name": "Ada Lovelace" },
"platform_fee_minor": 5000,
"return_url": "https://your-platform.com/checkout/complete"
}201 Created
{
"id": "pi_01K4A7BQ2N8XZ3RM5TVWD9GHJF",
"status": "requires_payment",
"amount_minor": 100000,
"currency": "TRY",
"checkout_url": "https://pay.finletry.com/c/01K4A7BQ2N…",
"expires_at": "2026-09-01T15:30:00Z",
"breakdown": {
"gross_minor": 100000,
"vat_minor": 16667,
"platform_fee_minor": 5000,
"seller_net_minor": 54249,
"tax_ruleset_version": "2026.02",
"estimated": true
},
"created_at": "2026-09-01T15:00:00Z"
}The breakdown carries estimated: true until capture. Rates resolve from a versioned rule set at the transaction’s own date, and every breakdown names the version it was computed under — which is what makes a figure from two years ago reproducible in a dispute. The authoritative numbers arrive on payment_intent.succeeded.
event.type drives the VAT rate. Omit it and the highest — safest — rate applies and the order is flagged for review, so always send it.
Step three
Verify, then act.
Delivery is at-least-once and retried with exponential backoff — 1s, 4s, 15s, 60s, 5m, 30m, 2h — before an event moves to a dead-letter queue a human reviews. Respond 2xx within ten seconds and stay idempotent on event.id.
POST /your/webhook
Finletry-Signature: t=1756742400,v1=5257a869e7ec…
{
"id": "evt_01K4A7BQ9V4TZ7PN2MQXY8JDHE",
"type": "payment_intent.succeeded",
"created_at": "2026-09-01T15:04:00Z",
"data": { "object": { "id": "pi_01K4A7BQ2N8XZ3RM5TVWD9GHJF" } }
}const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
// The RAW body — before any JSON parsing. Re-serialising changes
// key order and whitespace, and the signature stops matching.
const expected = hmacSha256(`${t}.${rawBody}`, webhookSecret);
if (!timingSafeEqual(expected, v1)) return reject();
if (now - Number(t) > 300) return reject(); // five-minute windowVerify against the raw request body, before any JSON parsing. Parsing and re-serialising changes key order and whitespace, and a signature computed over the result will never match — this is the single most common integration bug on any signed webhook.
Retries
Every mutating call is idempotent.
Send a fresh UUID per logical operation and retry with the same key. That is what makes a retry safe rather than a second charge — and the key is scoped to your API key, so it can never collide with another partner’s.
- Same key, same body
- The original response, replayed. No new side effect.
- Same key, different body
- 409 — the key is already bound to another request.
- Same key, still in flight
- 409 with Retry-After. The first call is still running.
- Key not seen for 24 hours
- Retention has expired; the call executes as new.
Errors
Branch on code, never on message.
code is stable. message is written for a developer reading a log and its wording can change without notice. Every response — successful or not — carries X-Request-Id; log it, and support becomes one message instead of three.
| HTTP | type | Meaning |
|---|---|---|
| 400 | invalid_request_error | Malformed or missing parameters |
| 401 | authentication_error | Missing, invalid or revoked API key |
| 403 | permission_error | Key is valid but not allowed for this resource |
| 404 | not_found_error | Resource does not exist |
| 409 | conflict_error | Idempotency conflict or invalid state transition |
| 422 | compliance_error | Blocked by a tax, identity or risk rule |
| 429 | rate_limit_error | Too many requests — honour Retry-After |
| 500 | api_error | Our fault. Safe to retry with the same idempotency key |
Only 429 and 500 are worth retrying with the same body. Everything else returns the same answer until something changes on your side — or, for 422, until the underlying condition does. A 422 is deliberately not a 400: the request was well formed and there is nothing in it to fix.
Sandbox
A complete environment, no real data.
The sandbox behaves identically but uses a simulated payment provider, issues no real documents and moves no real money. Identity verification resolves deterministically from the submitted values, so you can test the verified, failed and manual-review paths without touching real personal data.
- 4111 1111 1111 1111
- Success
- 4000 0000 0000 0002
- Declined
- 4000 0000 0000 3220
- 3DS challenge required
What isn’t here
The rest follows under NDA.
The full OpenAPI schema, the reporting and settlement endpoints, refund semantics across every leg, the organizer onboarding payloads and a sandbox key are shared during evaluation. What is on this page is what you need to size the work — the rest is what you need to do it.
Versioning: the path carries the major version and a breaking change ships as /v2 with a minimum twelve-month overlap. Additive changes — new fields, event types, enum values — can appear in v1 at any time, so parse defensively and ignore what you don’t recognise.
Send us your current payment flow.
We will map it to ours and tell you what changes — before your engineers spend a sprint finding out.