Geliştiriciler/Entegrasyon
Webhook'lar
Webhook, bir sipariş tamamlandığında CardV'nin sunucunuza gönderdiği bir mesajdır.
Sizi sürekli sorgulama yapmaktan kurtarır. Hiçbir zaman kod içermez: Webhook geldikten sonra siparişi
GET/api/v1/orders/{order_id} ile okuyun.
Webhook adreslerini Portal'dan ayarlarsınız (Owner, Integrations → Webhooks). Live ve Sandbox için ayrı ayrı ayarlanır. Bunun için bir API yoktur.
İlgili: Ürünler ve siparişler · Güvenlik
#Olaylar
| Olay | Siparişin yeni durumu |
|---|---|
order.succeeded | succeeded |
order.partially_succeeded | partially_succeeded |
order.failed | failed (bu henüz iade değildir) |
order.refunded | refunded (para cüzdanınıza geri döndü) |
- Her olay, bir sipariş ve bir Webhook adresi için en fazla bir kez gönderilir (yeniden denemeler hariç).
acceptedveprocessingdurumları ile bakiye yükleme için olay gönderilmez.
#Mesaj içeriği
CardV, HTTPS adresinize bir POST isteği gönderir:
POST /cardv/webhook HTTP/1.1
Content-Type: application/json
User-Agent: CardV-B2B-Webhook/1.0
X-CardV-Event: order.succeeded
X-CardV-Delivery: 5521
X-CardV-Timestamp: 1790000100
X-CardV-Signature: t=1790000100,v1=2baac51da272ee8d4b7b1382d48037976c7272f1749c9399a50e758604de8a27Gövde burada okunaklı olsun diye biçimlendirilmiştir (CardV tek satır olarak gönderir):
{
"event": "order.succeeded",
"order": {
"id": "O-00000001",
"order_id": "O-00000001",
"external_order_id": "TEST-0001",
"status": "succeeded",
"currency": "USD",
"total_amount": "9.2500",
"items": [
{
"sku_id": "S000001",
"product_name": "Example Card",
"quantity": 1,
"delivery_count": 1
}
]
}
}order.order_id, CardV sipariş numarasıdır.order.idde aynı değeri taşır.order.external_order_id, kendi sipariş numaranızdır.order.status, olay anındaki durumdur. Güncel olmayabilir.X-CardV-Delivery, bu mesajın numarasıdır. Portal'daki gönderim geçmişinde görünür.
#İmza kontrolü
Her Webhook, o Webhook'a ait imzalama sırrıyla (whsec_...) imzalanır.
Portal bu sırrı yalnızca bir kez gösterir: Webhook'u oluşturduğunuzda veya sırrı sıfırladığınızda.
İmza şöyle oluşturulur:
X-CardV-Signature: t=<Unix saniyesi>,v1=<küçük harfli hex>
v1 = HMAC-SHA256(anahtar = imzalama sırrı, mesaj = "<t>" + "." + ham gövde baytları)Adımlar:
- JSON'u ayrıştırmadan önce ham gövde baytlarını okuyun.
- Başlıktan
tvev1değerlerini alın. t, sizin saatinizden 300 saniyeden fazla farklıysa mesajı reddedin.- Beklenen
v1değerini hesaplayın ve sabit süreli (constant-time) bir karşılaştırmayla kontrol edin. - JSON'u ancak bundan sonra ayrıştırın.
Yalnızca t ve gövde imzalanır. Olay türünü X-CardV-Event başlığından değil,
gövdedeki event alanından alın.
Test verisi (sahte sır):
secret whsec_TEST_ONLY_not_a_real_secret_000000000000
t 1790000100
v1 2baac51da272ee8d4b7b1382d48037976c7272f1749c9399a50e758604de8a27Bu test verisinin gövdesi tam olarak şu tek satırdır (266 bayt, sonunda yeni satır yok):
{"event":"order.succeeded","order":{"id":"O-00000001","order_id":"O-00000001","external_order_id":"TEST-0001","status":"succeeded","currency":"USD","total_amount":"9.2500","items":[{"sku_id":"S000001","product_name":"Example Card","quantity":1,"delivery_count":1}]}}Python (Flask)
import hashlib, hmac, os, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["CARDV_WEBHOOK_SECRET"].encode()
def verify(raw_body: bytes, header: str, tolerance: int = 300) -> bool:
try:
parts = dict(item.split("=", 1) for item in header.split(","))
t, v1 = parts["t"].strip(), parts["v1"].strip()
except (KeyError, ValueError):
return False
if not t.isdigit() or abs(time.time() - int(t)) > tolerance:
return False
signed = t.encode() + b"." + raw_body
expected = hmac.new(SECRET, signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), v1.encode()) # constant time
@app.post("/cardv/webhook")
def cardv_webhook():
raw = request.get_data() # raw bytes, before JSON parsing
if not verify(raw, request.headers.get("X-CardV-Signature", "")):
abort(400)
event = request.get_json()
store_event(request.headers["X-CardV-Delivery"], event) # your code
return "", 204 # answer fast; do the work laterNode.js (Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.CARDV_WEBHOOK_SECRET;
function verify(rawBody, header, toleranceSeconds = 300) {
const parts = {};
for (const p of String(header || "").split(",")) {
const i = p.indexOf("=");
parts[p.slice(0, i).trim()] = p.slice(i + 1).trim();
}
const { t, v1 } = parts;
if (!t || !v1 || !/^\d+$/.test(t)) return false;
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(t)) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", SECRET)
.update(Buffer.concat([Buffer.from(`${t}.`, "utf8"), rawBody]))
.digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(v1, "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// express.raw keeps the exact bytes. Do not use express.json() on this route.
const rawJson = express.raw({ type: "application/json" });
app.post("/cardv/webhook", rawJson, async (req, res) => {
if (!verify(req.body, req.get("X-CardV-Signature"))) return res.sendStatus(400);
const event = JSON.parse(req.body.toString("utf8"));
await storeEvent(req.get("X-CardV-Delivery"), event); // your code
res.sendStatus(204);
});İki örnek de test verisini doğru şekilde doğrular.
#Yeniden denemeler
- Olayı kaydettikten sonra 15 saniye içinde herhangi bir 2xx durum koduyla yanıt verin. Zaman alan işleri (siparişi okumak, kodları göndermek) sonraya bırakın.
- Bunun dışındaki her şey başarısız sayılır: başka bir durum kodu, zaman aşımı veya bağlantı hatası.
- CardV yönlendirmeleri takip etmez. 3xx yanıtı başarısız sayılır. Son adresin tam halini kaydedin.
- Başarısız bir mesaj, toplam 6 denemeye kadar yeniden gönderilir:
| Deneme | Ne zaman |
|---|---|
| 1 | Olaydan hemen sonra |
| 2 | 1. deneme başarısız olduktan yaklaşık 1 dakika sonra |
| 3 | 2. deneme başarısız olduktan yaklaşık 5 dakika sonra |
| 4 | 3. deneme başarısız olduktan yaklaşık 15 dakika sonra |
| 5 | 4. deneme başarısız olduktan yaklaşık 30 dakika sonra |
| 6 | 5. deneme başarısız olduktan yaklaşık 60 dakika sonra |
Süreler bir dakikaya kadar gecikebilir. 6. başarısız denemeden sonra CardV göndermeyi bırakır. Tüm mesajları Portal'daki gönderim geçmişinde görebilir ve yeniden gönderebilirsiniz.
#Tekrarlanan mesajlar
Aynı olay birden fazla kez gelebilir ve olaylar sırasız gelebilir.
- Daha önce işlediğiniz bir olayı yok sayın.
Doğrulanmış gövdedeki
order.order_idveeventdeğerlerini birlikte kontrol edin. Portal'dan yeniden gönderilen mesaj yeni birX-CardV-Deliverynumarası alır. Bu yüzden tek başına o numara yeterli değildir. - Gövde, olayın gerçekleştiği andaki bir anlık görüntüdür. Siparişi her zaman yeniden okuyun ve güncel durumuna göre işlem yapın.
- Webhook'lar kaçabilir. Birkaç dakikadan eski, henüz tamamlanmamış siparişleri kontrol eden bir zamanlanmış görev de çalıştırın.
#Adres kuralları
- Adres
https://ile başlamalı ve internetten erişilebilen herkese açık bir adrese gitmelidir. Özel ve yerel adresler reddedilir. - Dört olayın hepsine veya yalnızca bazılarına abone olabilirsiniz.
- İmzalama sırrını sıfırlamak hemen geçerli olur. Eski sır çalışmayı bırakır. Kesinti olmadan geçiş yapmak için yeni sırla ikinci bir Webhook ekleyin, sunucunuza dağıtın, sonra eskisini devre dışı bırakın.
- CardV, sorun gidermek için yanıtınızın ilk 1.000 karakterini saklar. Yanıtınıza gizli bilgi veya kişisel veri koymayın.
Entegrasyonla ilgili sorunuz mu var? Merchant ID’nizi ve sipariş ya da istek kimliğini ekleyerek [email protected] adresine yazın.