Take payments with the Efespay API.
Open a payment session with a single REST API, get the result through a signed webhook, and refund or cancel on the same record. This page gathers what you need for the integration in one place.
The API and test environment are in pre-pilot preparation. Live mode opens once the business application is approved.
Quick start
Your first test payment in three steps: get a key, open a session, verify the result.
API base: https://api.efespay.com/v1
1. Get a test key
Create a secret test key in the panel under Developers → API keys. The key starts with
efp_test_sk_and is shown only once; store it as an environment variable on your server.2. Open a payment session
Send the amount in minor units (
amount_minor: 125000 = TRY 1,250.00) with your order reference. Redirect the customer to theurlin the response.Create a payment session curl https://api.efespay.com/v1/payment-sessions \ -H "Authorization: Bearer $EFESPAY_SECRET_KEY" \ -H "Idempotency-Key: order-1001" \ -H "Content-Type: application/json" \ -d '{ "order_reference": "ORDER-1001", "amount_minor": 125000, "currency": "TRY", "installment_options": [1, 3, 6], "return_url": "https://shop.example.com/order/1001" }'3. Verify the result
The customer returning to your site is not proof of payment. Before fulfilling the order, read the verified status with
GET /v1/payments/{id}or from the webhook.Retrieve the payment curl https://api.efespay.com/v1/payments/pay_… \ -H "Authorization: Bearer $EFESPAY_SECRET_KEY"
Authentication
Every request carries an API key in the Authorization header. The business and mode come from the key.
curl https://api.efespay.com/v1 \
-H "Authorization: Bearer $EFESPAY_SECRET_KEY"- Keys are used on the server only; never put them in a browser or mobile app.
- A request cannot specify another business: the business and mode (
test|live) come from the key. - 600 requests per minute per key; beyond that, 429
rate_limitedwith aRetry-Afterheader. - Every response carries
Efespay-Request-Id: req_…; share this ID in support requests. - Error messages are returned in Turkish or English according to the
Accept-Languageheader.
Payment sessions
A payment session is the request that opens the payment page for the customer. One payment record is created per order.
{
"id": "ps_…",
"object": "payment_session",
"status": "open",
"url": "https://efespay.com/pay/ps_…",
"payment_id": "pay_…",
"amount_minor": 125000,
"installment_options": [
{ "installments": 1, "total_minor": 125000, "fee_minor": 0, "monthly_minor": 125000 },
{ "installments": 3, "total_minor": 129875, "fee_minor": 4875, "monthly_minor": 43292 }
],
"expires_at": "2026-10-25T11:00:00.000Z"
}- Add an
Idempotency-Keyto write requests (≤ 255 characters). The same key and body return the stored response within 24 hours (Idempotent-Replayed: true). - The same key with a different body returns 409
idempotency_conflict; while the first request is running, 409idempotency_in_progress. - 5xx responses are not stored; you can retry with the same key.
- Even with a different key, a second open session cannot be created for the same order (409
order_reference_in_use).
Payment statuses
The browser return (return_url) is not proof of payment on its own. Update the order from the webhook or from the verified status in the GET /v1/payments/{id} response.
Webhook verification
Payment, refund and link events reach your server as signed POST requests. Verify the signature before processing the body.
POST https://shop.example.com/webhooks/efespay
Content-Type: application/json
User-Agent: Efespay-Webhooks/1.0
Efespay-Event-Id: evt_…
Efespay-Event-Type: payment.succeeded
Efespay-Signature: t=1792922400,v1=5f2c…{
"id": "evt_…",
"object": "event",
"type": "payment.succeeded",
"created_at": "2026-10-25T10:00:00.000Z",
"mode": "test",
"data": {
"object": {
"id": "pay_…",
"object": "payment",
"status": "succeeded",
"amount_minor": 125000,
"order_reference": "ORDER-1001"
}
}
}Read the t and v1 values from the Efespay-Signature header. Compute HMAC_SHA256(secret, t + "." + raw_body) as hex and compare it with v1 in constant time. Reject the request if t is older than 5 minutes. Verify the raw body before parsing it.
import crypto from "node:crypto";
export function verifyEfespaySignature(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 || "");
return fresh && a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: app.post("/webhooks/efespay", express.raw({ type: "application/json" }), (req, res) => {
// if (!verifyEfespaySignature(req.body.toString("utf8"), req.get("Efespay-Signature"), process.env.EFESPAY_WEBHOOK_SECRET)) return res.sendStatus(400);
// const event = JSON.parse(req.body); // deduplicate by event.id
// res.sendStatus(200);
// });- Your endpoint must return 2xx within 10 seconds; redirects (3xx) are not followed.
- A failed delivery is retried after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h (8 attempts in total).
- If there is no successful delivery for 3 days, the endpoint is disabled and the business owner is emailed.
- The same event can arrive more than once: deduplicate with
Efespay-Event-Id, and never roll a status back with an older event. - The signing secret (
whsec_…) is shown only once, when the endpoint is created and when it is rotated.
Error codes
API errors share one format; payment result errors are reported with a separate dictionary.
{
"error": {
"code": "order_reference_in_use",
"message": "There is an open payment with this order reference.",
"request_id": "req_…",
"param": "order_reference"
}
}Payment result error codes
On a failed payment, failure.code is one of the codes below; the provider's raw code is never shown to the customer and appears in the transaction detail in the panel. Technical codes are provider-side and counted separately from card declines.
Test cards
The Efespay test provider is ready in test mode in every account. Each card triggers a scenario; no real money moves.
- Test cards work only in test mode and with the Efespay test provider.
- No card number is entered on the test provider page; only the scenario you choose is sent to the server.
- The timeout scenarios let you try the uncertain result and reconciliation.
- Test events also arrive as webhooks; try your verification code before going live.
API reference
Every endpoint, field and example is in the OpenAPI document. The reference is published by the backend and updated together with the API.
Let's plan the integration together.
Describe your current payment flow and technical setup; let's agree together on test environment access and pilot scope.