API reference

Webhook'lar

Webhook'lar, bir şey olduğunda sunucunuza haber verir: bir ödeme başarılı oldu, bir iade tamamlandı, bir itiraz açıldı. Sürekli sorgulama (polling) yerine bunları kullanın; müşteri bankanın sayfasından hiç dönmediğinde de gerçeğin kaynağı olarak webhook'lara güvenin.

Uç nokta kaydı

Uç noktaları Geliştiriciler → Webhook'lar altında veya API ile oluşturun. URL herkese açık bir https adresi olmalı; ayrıca kaydettiğiniz bir web sitesinde bulunmalı veya Ayarlar → Web siteleri ve geri dönüş adresleri altında kaydettiğiniz bir geri dönüş (callback) adresiyle eşleşmelidir (aksi halde 400 webhook_url_not_allowed).

{
  "url": "https://shop.example.com/webhooks/pf",
  "enabled_events": ["payment_intent.succeeded", "payment_intent.payment_failed", "refund.succeeded", "refund.failed"],
  "description": "Order system"
}

enabled_events, istediğiniz olay türlerini listeler; tümü için ["*"] kullanın.

{
  "id": "we_01J9ZQC8D0F2H4K6M8P1R3T5V7",
  "object": "webhook_endpoint",
  "url": "https://shop.example.com/webhooks/pf",
  "enabled_events": ["payment_intent.succeeded", "payment_intent.payment_failed", "refund.succeeded", "refund.failed"],
  "status": "enabled",
  "description": "Order system",
  "secret": "REPLACE_WITH_THE_SECRET_FROM_THE_RESPONSE",
  "created": 1790000200
}

İmzalama gizli anahtarı bu yanıtta yalnızca bir kez döndürülür. Diğer gizli bilgilerinizle birlikte saklayın. Kaybolduysa veya sızdıysa POST /v1/webhook_endpoints/{id}/roll_secret ile yenileyin; yanıt yeni gizli anahtarı taşır ve yalnızca bir kez gösterilir.

Olay

Her teslimat, gövdesi bir JSON (olay) olan bir POST isteğidir:

{
  "id": "evt_01J9ZQ4V8K2M3N5P7R9T1W3Y5E",
  "object": "event",
  "type": "payment_intent.succeeded",
  "created": 1790000010,
  "merchant_id": "mer_01J9ZPZZ0A1B2C3D4E5F6G7H8J",
  "api_version": "2026-10-01",
  "livemode": false,
  "aggregate_type": "payment_intent",
  "aggregate_id": "pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5A",
  "sequence": 4,
  "request_id": "req_01J9ZQ4V8K2M3N5P7R9T1W3Y5F",
  "data": {
    "object": {
      "id": "pi_01J9ZQ4V8K2M3N5P7R9T1W3Y5A",
      "object": "payment_intent",
      "amount": 12500,
      "currency": "try",
      "status": "succeeded",
      "capture_method": "automatic",
      "created": 1790000000,
      "livemode": false
    },
    "previous_attributes": {"status": "processing"}
  }
}

data.object, değişiklikten sonraki API nesnesidir ve API'nin döndürdüğüyle birebir aynıdır; previous_attributes değişen alanları içerir. sequence, aynı nesnenin her değişikliğinde bir artar ve bir nesnenin bir uç noktaya yapılan teslimatları bu sırayla ulaşır. Olaylar kart numarası içermez.

Olay türleri

Olay data.object
payment_intent.created PaymentIntent
payment_intent.requires_action PaymentIntent
payment_intent.processing PaymentIntent
payment_intent.amount_capturable_updated PaymentIntent
payment_intent.succeeded PaymentIntent
payment_intent.payment_failed PaymentIntent
payment_intent.canceled PaymentIntent
payment_intent.fraud_updated PaymentIntent
refund.created Refund
refund.updated Refund
refund.succeeded Refund
refund.failed Refund
dispute.created Dispute
dispute.updated Dispute
dispute.closed Dispute
payout.created Payout
payout.paid Payout
payout.failed Payout
topup.succeeded Topup
balance.available Balance
balance.negative_limit_exceeded Balance
merchant.updated Merchant
topup.updated Topup
customer.created Customer
customer.updated Customer
customer.deleted Customer

Olaylar değiştirilemez ve saklanır; bu nedenle daha sonra GET /v1/events ve GET /v1/events/{id} ile okuyabilir, POST /v1/events/{id}/resend ile bir teslimatı yeniden isteyebilirsiniz.

İmzayı doğrulama

URL'nizi bilen herkes ona istek gönderebilir; bu yüzden her teslimatı doğrulayın. Başlık şöyledir:

PF-Signature: t=1790000011,v1=<64 hexadecimal characters>

t, teslimatın imzalandığı unix zamanıdır; v1 ise t + "." + body dizesinin imzalama gizli anahtarınızla hesaplanan onaltılık (hex) HMAC-SHA256 değeridir. Doğrulamak için:

  1. Ham istek gövdesini alındığı haliyle okuyun. Önce ayrıştırıp yeniden kodlamayın.
  2. Başlığı bölün; t değerini ve her v1 değerini alın.
  3. t, saatinizden 5 dakikadan fazla uzaksa reddedin (tekrar oynatma saldırılarına karşı korur).
  4. HMAC_SHA256(secret, t + "." + body) değerini hex olarak hesaplayın ve her v1 ile sabit zamanlı (constant time) karşılaştırın. Biri eşleşirse kabul edin.

Gizli anahtar yenileme sırasında birden fazla v1 bulunabilir.

<?php
function verifyPfSignature(string $payload, string $header, string $secret, int $tolerance = 300): bool
{
    $t = null;
    $signatures = [];
    foreach (explode(',', $header) as $part) {
        [$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');
        if ($key === 't') {
            $t = $value;
        } elseif ($key === 'v1') {
            $signatures[] = $value;
        }
    }
    if ($t === null || !ctype_digit($t) || abs(time() - (int) $t) > $tolerance) {
        return false;
    }
    $expected = hash_hmac('sha256', $t . '.' . $payload, $secret);
    foreach ($signatures as $signature) {
        if (hash_equals($expected, $signature)) {
            return true;
        }
    }
    return false;
}

$payload = file_get_contents('php://input');
$header = $_SERVER['HTTP_PF_SIGNATURE'] ?? '';
if (!verifyPfSignature($payload, $header, getenv('PF_WEBHOOK_SECRET'))) {
    http_response_code(400);
    exit;
}
$event = json_decode($payload, true);
http_response_code(200);
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyPfSignature(rawBody, header, secret, toleranceSeconds = 300) {
  let t = null;
  const signatures = [];
  for (const part of String(header ?? '').split(',')) {
    const [key, value = ''] = part.trim().split('=');
    if (key === 't') t = value;
    if (key === 'v1') signatures.push(value);
  }
  if (t === null || !/^\d+$/.test(t)) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((sig) => {
    const given = Buffer.from(sig, 'hex');
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

// Express: req.body'nin ham Buffer olması için express.raw({ type: 'application/json' }) kullanın.
// app.post('/webhooks/pf', express.raw({ type: 'application/json' }), (req, res) => {
//   if (!verifyPfSignature(req.body.toString('utf8'), req.get('PF-Signature'), process.env.PF_WEBHOOK_SECRET)) return res.sendStatus(400);
//   const event = JSON.parse(req.body.toString('utf8'));
//   res.sendStatus(200);
// });
package webhook

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"math"
	"strconv"
	"strings"
	"time"
)

// Verify, bir PF-Signature başlığını ham istek gövdesine karşı doğrular.
func Verify(payload []byte, header, secret string, tolerance time.Duration, now time.Time) bool {
	var t string
	var signatures []string
	for _, part := range strings.Split(header, ",") {
		key, value, _ := strings.Cut(strings.TrimSpace(part), "=")
		switch key {
		case "t":
			t = value
		case "v1":
			signatures = append(signatures, value)
		}
	}
	ts, err := strconv.ParseInt(t, 10, 64)
	if err != nil || math.Abs(float64(now.Unix()-ts)) > tolerance.Seconds() {
		return false
	}
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(t + "."))
	mac.Write(payload)
	expected := mac.Sum(nil)
	for _, sig := range signatures {
		given, err := hex.DecodeString(sig)
		if err == nil && hmac.Equal(given, expected) {
			return true
		}
	}
	return false
}
import hashlib
import hmac
import time


def verify_pf_signature(payload: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    t = None
    signatures = []
    for part in (header or "").split(","):
        key, _, value = part.strip().partition("=")
        if key == "t":
            t = value
        elif key == "v1":
            signatures.append(value)
    if t is None or not t.isdigit() or abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b"." + payload, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, sig) for sig in signatures)


# Flask: verify_pf_signature(request.get_data(), request.headers.get("PF-Signature", ""), SECRET)

Yanıt verme ve yeniden deneme

Kontrol listesi