Uygulama Webhooks
Uygulamanız üç tür bildirim alır: uygulama kaldırıldığında T-SOFT'un gönderdiği kaldırma bildirimi, veri silmeniz gerektiğinde gelen gizlilik bildirimleri ve mağazadaki olaylar için Admin API'den kendinizin kurduğu olay abonelikleri.
Uygulama kaldırıldı
Uygulama kaydınızda uninstall_url tanımlıysa, mağaza uygulamayı kaldırdığında bu adrese bir POST gelir:
{ "token": "<jwt>" }
Token, oturum token'ı ile aynı anahtarla ES256 imzalıdır; aynı JWKS adresiyle doğrulayın.
| Claim | Değer |
|---|---|
iss | https://api.tsoftapps.com |
aud | Uygulamanızın prefix'i |
event | app.uninstalled |
store_id | Uygulamayı kaldıran mağaza |
dest | Mağaza alan adı |
exp | Gönderimden 5 dakika sonra |
const { payload } = await jwtVerify(body.token, jwks, {
algorithms: ["ES256"],
issuer: "https://api.tsoftapps.com",
audience: process.env.TSOFT_APP_PREFIX,
})
if (payload.event !== "app.uninstalled") return res.sendStatus(400)
await stores.forget(payload.store_id)
res.sendStatus(200)
event alanını mutlaka kontrol edin. Oturum token'ında bu alan yoktur; ikisini birbirinin yerine kabul etmeyin.Adres yönlendirme izlenmeden çağrılır ve 10 saniye içinde 2xx dönmelidir. Başarısız gönderim 1 dakika ve 5 dakika sonra iki kez daha denenir. Bildirim gelmese de o mağazadaki erişiminiz kapandığı için o mağaza için token istekleriniz başarısız olur; bunu da kaldırma işareti sayabilirsiniz.
Gizlilik webhook'ları
Mağaza ya da müşteri verisi tutan her uygulama, bu verinin silinmesi gereken anlarda bildirim almalıdır. Uygulama kaydınıza (tsoft.app.toml → [webhooks] privacy_url) bir https:// adresi yazın; iki olay bu adrese gelir.
event | Ne zaman | Ek claim | Yapmanız gereken |
|---|---|---|---|
shop.redact | Uygulama mağazadan kaldırıldıktan 48 saat sonra. Bu sürede yeniden kurulursa gönderilmez. | — | O mağazaya ait tüm veriyi silin |
customers.redact | Mağaza bir müşteriyi sildiğinde (panelden, Admin API'den ya da müşteri hesabını kendisi kapattığında) | customer_ids: silinen müşterilerin kimlikleri | Bu müşterilere ait veriyi silin |
Gövde, imza ve claim'ler Uygulama kaldırıldı bildirimiyle aynıdır: { "token": "<jwt>" }, ES256, aynı JWKS. Olayı event claim'inden ayırın.
const { payload } = await jwtVerify(body.token, jwks, {
algorithms: ["ES256"],
issuer: "https://api.tsoftapps.com",
audience: process.env.TSOFT_APP_PREFIX,
})
if (payload.event === "shop.redact") {
await queue.add("redact-store", { storeId: payload.store_id })
} else if (payload.event === "customers.redact") {
await queue.add("redact-customers", { storeId: payload.store_id, customerIds: payload.customer_ids })
} else {
return res.sendStatus(400)
}
res.sendStatus(200)
Gönderim kuralları kaldırma bildirimiyle aynıdır: yönlendirme izlenmez, 10 saniye içinde 2xx beklenir, başarısız gönderim 1 ve 5 dakika sonra tekrar denenir. Aynı olay birden fazla gelebilir; silme işleminiz tekrar çalıştığında hata vermemelidir.
Mağaza olaylarına abone olma
Olay abonelikleri Admin API'nin webhooks kaynağıyla yönetilir ve write_webhooks kapsamını gerektirir. Bu kapsam onay ekranında hassas olarak işaretlenir.
curl -X POST https://magaza.com/api/v3/admin/2026-01/config/webhooks \
-H "Authorization: Bearer <access_token>" \
-H "Accept: application/vnd.api+json" \
-H "Content-Type: application/vnd.api+json" \
-d '{
"data": {
"type": "webhooks",
"attributes": {
"payloadUrl": "https://uygulamaniz.com/webhooks/tsoft",
"payloadSecret": "<kendi ürettiğiniz rastgele değer>",
"format": "2026-01",
"events": ["order.created", "order.status_changed"]
}
}
}'
| Alan | Açıklama |
|---|---|
payloadUrl | Olayların gönderileceği adres. Alan adı olmalı; IP adresi kabul edilmez. |
payloadSecret | İmza anahtarı. Her mağaza için ayrı ve rastgele üretin. |
format | 2026-01. Verilmezse olay adlarından çıkarılır: konu adları (order.created) 2026-01 sayılır. |
events | Abone olunan konular (aşağıdaki tablo). |
timeout | Yanıt bekleme süresi, saniye: 3, 5 ya da 7. Varsayılan 3. |
Uygulamanız yalnız kendi açtığı abonelikleri görür: listeleme, güncelleme, silme, ping ve teslimat geçmişi (config/webhook-deliveries?filter[webhookId]=) yalnız bu kayıtlar için çalışır. Mağazanın kendi aboneliklerine ve başka uygulamalarınkine erişemezsiniz; bunlar yokmuş gibi 404 döner. Uygulama kaldırılınca açtığınız abonelikler kendiliğinden silinir.
Uygulama abonelikleri mağazanın elle ekleyebileceği webhook sınırına sayılmaz ve mağazanın paketinden bağımsızdır. Bir mağazada uygulamaların toplam abonelik sınırı 50'dir.
Konular
| Konu | Tetiklenme | data | Gereken kapsam |
|---|---|---|---|
order.created | Sipariş oluşturulduğunda | orders | read_orders |
order.status_changed | Sipariş durumu değiştiğinde | orders | read_orders |
order.invoice_updated | Fatura bilgisi güncellendiğinde | orders | read_orders |
order.shipped | Sipariş kargoya verildiğinde | orders | read_orders |
order.provision_report_updated | Provizyon raporu sonuçlandığında | orders | read_orders |
order.transferred | Sipariş aktarıldığında | orders | read_orders |
customer.created | Üye kaydolduğunda | customers | read_customers |
customer.deleted | Üye silindiğinde | { type, id } | read_customers |
customer.notification_settings_updated | Üyenin e-posta/SMS izni değiştiğinde | customers | read_customers |
comment.created | Ürün yorumu yazıldığında | product-comments | read_reviews |
message.created | Müşteri mesajı geldiğinde | customer-messages | read_customer_messages |
return.created | İade talebi oluşturulduğunda | order-returns | read_returns |
return.updated | İade talebi güncellendiğinde | order-returns | read_returns |
return.status_changed | İade durumu değiştiğinde | order-returns | read_returns |
product.created | Ürün eklendiğinde | products | read_products |
product.updated | Ürün güncellendiğinde | products | read_products |
product.deleted | Ürün silindiğinde | { type, id } | read_products |
product.stock_changed | Ürün stoku değiştiğinde | products | read_products |
category.created | Kategori eklendiğinde | categories | read_products |
category.updated | Kategori güncellendiğinde | categories | read_products |
category.deleted | Kategori silindiğinde | { type, id } | read_products |
Bildirim konuları (notification.*) yalnız mağazanın kendi aboneliklerine açıktır; uygulamalar bu konuları alamaz.
read_orders olmadan order.created hiç gelmez. Kurulumda gereken kapsamları isteyin.product.updated ve product.stock_changed toplu güncellemelerde kısa sürede çok sayıda olay üretebilir; yalnız gerçekten kullandığınız konulara abone olun.
Gövde
Her olay aynı zarfla gelir. data, aynı kaydı Admin API 2026-01'de GET ile okuduğunuzda dönen data nesnesidir: alanlar, adlar ve biçim API ile aynıdır.
{
"id": "6af384d126c5ea4a417fb6b6",
"topic": "order.created",
"api_version": "2026-01",
"created_at": "2026-11-11T09:30:12.000Z",
"shop": { "id": "6eaab115-…", "domain": "magaza.tsoft.biz", "primary_domain": "magaza.com" },
"data_format": "2026-01",
"data": {
"type": "orders",
"id": "48213",
"attributes": { "orderNumber": "TS-48213", "customerEmail": null, "…": "…" }
}
}
shop.domain mağazanın kalıcı adresidir (*.tsoft.biz); mağaza alan adını değiştirse de aynı kalır, mağazayı bununla (ya da shop.id ile) eşleyin. shop.primary_domain mağazanın kullandığı alan adıdır; müşteriye gösterilecek bağlantılar için kullanın.
Belge, uygulamanızın yetkileriyle üretilir. API'de göremediğiniz alanı webhook'ta da göremezsiniz. read_customer_personal_data kapsamı olmayan uygulamaya müşterinin adı, e-postası, telefonu ve adresi null gelir; alan gövdede kalır. Silinen kayıtlarda data yalnız { "type": "products", "id": "…" } taşır.
Başlıklar
| Başlık | Değer |
|---|---|
X-Tsoft-Hmac-Sha256 | Gövdenin imzası: base64(HMAC-SHA256(payloadSecret, ham gövde)) |
X-Tsoft-Topic | Konu, ör. order.created |
X-Tsoft-Api-Version | 2026-01 |
X-Tsoft-Event-Id | Olayın kimliği; aynı olayın tekrar denemelerinde aynıdır |
X-Tsoft-Webhook-Id | Bu teslimatın kimliği |
X-Tsoft-Shop-Id | Mağaza kimliği (store_id ile aynı) |
X-Tsoft-Shop-Domain | Mağazanın kalıcı adresi (*.tsoft.biz); gövdede shop.domain |
X-Tsoft-Triggered-At | Olayın zamanı (ISO 8601) |
İmzayı doğrulama
İmzayı ham gövde üzerinden hesaplayın; JSON'u çözüp yeniden yazdığınız metin farklı olur. Karşılaştırmayı sabit süreli yapın.
import { createHmac, timingSafeEqual } from "node:crypto"
app.post("/webhooks/tsoft", express.raw({ type: "*/*" }), async (req, res) => {
const expected = createHmac("sha256", process.env.TSOFT_WEBHOOK_SECRET!).update(req.body).digest()
const received = Buffer.from(req.header("X-Tsoft-Hmac-Sha256") ?? "", "base64")
if (received.length !== expected.length || !timingSafeEqual(received, expected)) return res.sendStatus(401)
const event = JSON.parse(req.body.toString("utf8"))
if (await seen(event.id)) return res.sendStatus(200)
await queue.add(event.topic, event)
res.sendStatus(200)
})
Teslim kuralları
2xxbaşarılı sayılır. Yönlendirme izlenmez.- Yanıt
timeoutsüresi içinde gelmelidir; gelmezse başarısız sayılır. - Başarısız teslimat, ilk denemeden sonra 7 kez daha denenir: 1, 5, 15 ve 30 dakika, 1, 1 ve 2 saat arayla (toplam yaklaşık 5 saat).
404ve410tekrar denenmez.- Aynı olay birden fazla gelebilir ve sıra garantisi yoktur.
id(X-Tsoft-Event-Id) ile tekrarı ayıklayın, kaydın güncel halini gerekirse API'den okuyun.
Eski olay adları
ORDER.CREATE gibi büyük harfli adlar ve format: "legacy" eski uygulamalar için çalışmaya devam eder. Bu abonelikler eski gövdeyi alır: Admin API biçiminde değildir ve yetkiye göre maskelenmez. Yeni uygulamalarda kullanmayın. ORDER.BEFORE (siparişi tamamlanmadan durdurabilen senkron çağrı) 2026-01'de yoktur.
Endpoint önerileri
- Hemen
2xxdönün, işi kuyruğa alın; yavaş yanıt gönderimi başarısız sayar. - Her isteğin imzasını
X-Tsoft-Hmac-Sha256ile doğrulayın;payloadSecret'i ortam değişkeninde ya da gizli anahtar deposunda saklayın. - Olayları
idile tekilleştirin. - Gövdeyi yalnız ihtiyaç duyduğunuz kadar saklayın; kişisel veri tutuyorsanız gizlilik webhook'larını uygulayın.