Abonelik Webhook'u
Abonelik subscription.webhook_url ile açıldıysa, aboneliğin durumu değiştiğinde gateway sunucusu bu adrese imzalı bir POST gönderir. webhook_url verilmeyen aboneliklere webhook gönderilmez.
Webhook yalnızca abonelikler için vardır; tek seferlik ödemeler ve siparişler için webhook gönderilmez.
İstek
POST /odemehub/abonelik HTTP/1.1
Host: magazam.com
Content-Type: application/json
X-Signature: 9a3f...e1
{"result":{"successful":true,"message":null},"event":"active","subscription":{...}}
| Başlık | Değer |
|---|---|
Content-Type | application/json |
X-Signature | Gövdenin gizli anahtarla HMAC-SHA256 imzası, küçük harf hex. API yanıtlarıyla aynı yöntem. |
Gövde
{
"result": {
"successful": true,
"message": null
},
"event": "active",
"subscription": {
"token": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"channel_token": "6f1c2e7a-4b3d-4c8e-9a61-2f5d7b0c3e14",
"channel_reference": "UYELIK-4471",
"status": "active",
"period": "monthly",
"items": [
{ "channel_reference": "PREMIUM-AYLIK", "name": "Premium üyelik", "quantity": 1, "unit_amount": "149.90", "tax_rate": "20.00" }
],
"amount": "149.90",
"currency": "TRY",
"is_test": false,
"starts_at": "2026-09-29T10:15:00.000000Z",
"ends_at": "2026-10-29T10:15:00.000000Z",
"paid_at": "2026-09-29T10:15:00.000000Z",
"cancelled_at": null,
"checkout_url": null
}
}
| Alan | Tip | Açıklama |
|---|---|---|
result | object | Her zaman {"successful": true, "message": null}. |
event | string | Olay. Aşağıya bakın. |
subscription | object | Aboneliğin olay anındaki hâli: Abonelik nesnesi. |
Olaylar
Olay, aboneliğin ulaştığı durumu bildirir.
event | Ne zaman gönderilir | subscription.status |
|---|---|---|
active | Bir dönem ödendi: ilk ödeme, otomatik yenileme ya da past_due dönemin checkout_url üzerinden ödenmesi. | active |
past_due | Yenileme dönemi kayıtlı karttan tahsil edilemedi. subscription.checkout_url dönemin ödeme sayfasıdır. | past_due |
cancelled | Abonelik iptal edildi. | Ödenmiş dönem sürüyorsa active, değilse cancelled |
ended | Abonelik sona erdi; tahsilat yapılmayacak. İptal edilen aboneliğin ödenmiş dönemi bittiğinde ya da ödenmiş dönemi olmayan abonelik iptal edildiğinde gönderilir. | cancelled |
past_due zamanlaması
Yenileme dönemi başladığında tutar müşterinin varsayılan kayıtlı kartından çekilir. Çekim başarısız olursa 3, 6, 9 ve 12 saat aralıklarla yeniden denenir (toplam 5 deneme). Bu süre boyunca abonelik active kalır. Son deneme de başarısız olursa past_due gönderilir. Müşterinin kayıtlı kartı yoksa yeniden deneme yapılmaz ve past_due doğrudan gönderilir.
Çalışma alanı işlem yapamıyorsa (403 durumları) yenileme çekimi yapılmaz, deneme sayılmaz ve abonelik bu sürede past_due olmaz.
İmza doğrulama
Gövdeyi ham hâliyle okuyun ve X-Signature başlığıyla doğrulayın. Gövdeyi ayrıştırıp yeniden yazan bir katman (ör. Express'te express.json()) imzayı bozar. İmza tutmuyorsa bildirimi işlemeyin.
- PHP
- Node.js
- Python
- Java
- Elle
use Gurmehub\Odemehub\Exception\SignatureException;
try {
$webhook = $client->subscriptionWebhook(
file_get_contents('php://input'),
$_SERVER['HTTP_X_SIGNATURE'] ?? null,
);
} catch (SignatureException $exception) {
http_response_code(400);
exit;
}
$webhook->event; // active | past_due | cancelled | ended
$webhook->subscription; // retrieveSubscription() ile aynı nesne
http_response_code(200);
import express from 'express';
import { SignatureError } from '@odemehub/node-sdk';
app.post('/odemehub/abonelik', express.raw({ type: 'application/json' }), (req, res) => {
let webhook;
try {
webhook = client.subscriptionWebhook(req.body, req.get('X-Signature'));
} catch (error) {
if (error instanceof SignatureError) {
return res.sendStatus(400);
}
throw error;
}
webhook.event; // active | past_due | cancelled | ended
webhook.subscription; // retrieveSubscription() ile aynı nesne
res.sendStatus(200);
});
from odemehub import SignatureError
@app.post("/odemehub/abonelik")
def abonelik_bildirimi():
try:
webhook = client.subscription_webhook(
request.get_data(), # Django: request.body
request.headers.get("X-Signature"),
)
except SignatureError:
return "", 400
webhook.event # active | past_due | cancelled | ended
webhook.subscription # retrieve_subscription() ile aynı nesne
return "", 200
import com.odemehub.exception.SignatureException;
@PostMapping("/odemehub/abonelik")
public ResponseEntity<Void> abonelikBildirimi(
@RequestBody byte[] payload,
@RequestHeader(value = "X-Signature", required = false) String signature
) {
com.odemehub.response.SubscriptionWebhook webhook;
try {
webhook = client.subscriptionWebhook(payload, signature);
} catch (SignatureException exception) {
return ResponseEntity.badRequest().build();
}
webhook.getEvent(); // active | past_due | cancelled | ended
webhook.getSubscription(); // retrieveSubscription() ile aynı nesne
return ResponseEntity.ok().build();
}
# Ham gövde body.json'da, başlıktaki imza $RECEIVED değişkeninde
EXPECTED=$(openssl dgst -sha256 -hmac "$ODEMEHUB_API_SECRET" < body.json | sed 's/^.* //')
[ "$EXPECTED" = "$RECEIVED" ] && echo "imza geçerli" || echo "imza geçersiz"
SDK'lar ayrıca isActive(), isPastDue(), isCancelled(), isEnded() (Python'da is_active() …) yardımcılarını sağlar.
Yanıt ve yeniden deneme
| Kural | Değer |
|---|---|
| Beklenen yanıt | Herhangi bir 2xx. Yanıt gövdesi kullanılmaz. |
| Zaman aşımı | 15 saniye. |
| Başarısız sayılan | 2xx dışı yanıt, bağlantı hatası ya da zaman aşımı. |
| Yeniden deneme | 5 dakika sonra 1 kez. Toplam 2 deneme; sonrasında yeniden gönderilmez. |
Teslim edilemeyen bildirimler panelde aboneliğin sayfasında HTTP kodu ve yanıtıyla listelenir.
Tekrar ve sıra
- Webhook gövdesinde olay kimliği ya da idempotency anahtarı yoktur. Yanıtınız gateway'e ulaşmazsa (ör. 15 saniyeyi aşarsa) aynı bildirim yeniden gönderilir; işleyicinin aynı bildirimi ikinci kez aldığında sonucu değiştirmemesi gerekir.
activeolayı her ödenen dönem için ayrı gönderilir; dönemlersubscription.starts_atvesubscription.ends_atdeğerleriyle ayrılır.- Bildirimlerin sırası için bir garanti tanımlı değildir. Aboneliğin güncel hâli
retrieve-subscriptionile alınır.