Capi Agent API — Özel mağaza
Mağazanızın sistemi ürünlerini, siparişlerini, terk edilmiş sepetlerini ve müşterilerini gönderir; Capi Agent'ın asistanı, kampanyaları ve otomasyonları bir Salla veya Shopify mağazasındakiyle aynı çalışır. Ters yönde sisteminiz imzalı olaylar alır: indirim kodları, müşterinin siparişi onaylaması veya iptal etmesi ve abonelikten çıkışlar.
Mağazanızı bağlamak için kılavuzu Claude veya ChatGPT'ye yapıştırın ya da OpenAPI dosyasını Postman veya Insomnia'ya aktarın.
https://capiagent.com/api/ingest/v1Bu sayfada
Hızlı başlangıç
- 1Capi Agent panelinde Entegrasyonlar → Özel mağaza'yı açın ve önce bir test anahtarı oluşturun.
- 2Anahtarı GET /ping ile doğrulayın.
- 3Ürünleri POST /products ile, her siparişi POST /orders ile, durum değişikliklerini PATCH /orders/{id} ile gönderin.
- 4Test anahtarıyla tüm istekler başarılı olunca canlı bir anahtar oluşturup değiştirin.
- 5İsteğe bağlı: kuponları, sipariş onaylarını ve abonelik iptallerini almak için bir olay adresi ekleyin.
- 6Widget satırını her sayfaya ekleyin, üç olay için AISA.track ve giriş yapmış müşteri için AISA.identify çağırın («Widget ve tarayıcı olayları» bölümü).
curl -X GET https://capiagent.com/api/ingest/v1/ping \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"Kimlik doğrulama ve anahtarlar
Anahtarı her istekte Authorization: Bearer <key> olarak, istemciniz Authorization ayarlayamıyorsa X-API-Key olarak gönderin. Anahtar oluşturulduğunda bir kez gösterilir; yalnızca SHA-256 özetini saklarız.
Bir anahtar tek bir markayı açar; hiçbir istek başka bir markayı belirtemez.
Test anahtarları
capi_test_ ile başlayan anahtar her isteği doğrular ve tamamen aynı sonuçlarla yanıt verir — ama hiçbir şey yazmaz: ürün, sipariş veya mesaj yok. Her yanıtında "test": true bulunur.
Genel kurallar
- Tutarlar
- Ana birimde (199.99), kuruş değil — metin değil JSON sayısı olarak: metin olarak gönderilen her tutar ("199.99" bile) para birimine göre tahminle okunur ve uyarı alır. Markanın para birimine göre çevrilir — KWD, BHD ve OMR için üç ondalık.
- Telefon numaraları
- Uluslararası, yalnızca rakam (966501234567). Müşterinin WhatsApp kimliğidir ve asistan siparişlerini bununla bulur. Yerel numara uyarıyla reddedilir.
- Tekrar güvenliği
- Her yazma sizin kimliğinizle eşleşir; aynı isteği yeniden göndermek çoğaltmaz, günceller. Gönül rahatlığıyla yeniden deneyin.
- Tarihler
- Saat dilimiyle ISO 8601 (2026-10-06T09:00:00Z). Saat dilimi olmayan tarih sunucu saatiyle okunur ve uyarı alır.
- Sonuçlar
- 200 yanıtı her öğe için bir sonuç taşır; hatalı bir öğe isteğin geri kalanını düşürmez. Başarısız öğede errorCode bulunur; tanınmayan her alan adı warnings içinde uyarı olarak döner.
- Eksik alan ve null
- POST /products ve POST /orders kaydı değiştirir: her seferinde tüm alanları gönderin. POST /customers birleştirir ve hiçbir şeyi silmez. PATCH yalnızca gönderdiğinizi değiştirir; null alanı siler — costPrice, shipment ve refunded hariç, bunlarda null yok sayılır (refunded'ı sıfırlamak için 0 gönderin); shipment içindeki null yalnızca o alt alanı siler. statusName yalnızca status ile birlikte okunur.
- Metin ve kodlar
- Mesajlar ve warnings okumak içindir, ifadesi değişebilir. Kodunuzu yalnızca error.code, errorCode ve warningCodes üzerine kurun.
- Olay sırası
- Olayların sırayla gelmesi garanti değildir; yeniden deneme daha yeni bir olaydan sonra gelebilir. id ile tekrarı önleyin, olayın kendi zamanına göre sıralayın (respondedAt, unsubscribedAt veya createdAt).
- Uyumluluk
- v1 içinde hiçbir şey silinmez veya yeniden adlandırılmaz; yalnızca alan, olay, durum ve hata kodu eklenir. Kodunuz tanımadığı her şeyi yok saysın; her eklemeyle çalışmaya devam eder.
Değişiklik günlüğü
API'ye eklenen her şey, en yenisi önce. v1'den hiçbir şey kaldırılmadı.
- 16.10.2026
- Toplu istekte başarısız her öğede errorCode.
- warnings içinde uyarılar: tanınmayan alan adı, metin olarak gönderilen tutar, saat dilimi olmayan tarih, desteklenmeyen para birimi.
- Her webhook olayında apiVersion ve X-Capi-Api-Version başlığı.
- Aynı orderId için kuponu yeniden kullanmak already_used yerine başarı (replayed) döner.
- warnings yanında warningCodes, complete ile reconciled ve 422 all_failed içinde her öğenin sonucu.
- İmza başlığı birden fazla v1 taşıyabilir (sır değişimi için); herhangi biri eşleşirse geçerlidir.
- Kuponlarda discountType ve sayı olarak usageLimit.
- Yayınlanan kurallar: eksik alan ve null, metin ve kodlar, olay sırası.
- PATCH /products: price ile birlikte salePrice: null göndermek indirimi bitirir ve indirim öncesi fiyatı siler.
- 15.10.2026
- AISA.identify içinde imzalı müşteri kimliği (hash): konuşmalar cihazlar arasında birleşir.
- 08.10.2026
- Reklam satışlarının sayılması için POST /orders içinde tracking.
- Siparişlerde items[].productId ve couponCode.
- Mağazada widget ve tarayıcı olayları.
- Mağaza para birimi ilk gelen veriden belirlenir.
- 06.10.2026
- Test anahtarları (capi_test_).
- POST /customers ve PATCH /products/:id.
- Yeniden denemeli order.cod_confirmed, order.cod_cancelled ve customer.unsubscribed olayları.
- Kupon kontrolü, kullanımı ve geri alınması.
- OpenAPI dosyası ve geliştirici sayfası.
- 05.10.2026
- Terk edilmiş sepetler için POST /carts.
- coupon.create olayı.
- 21.09.2026
- İlk sürüm: GET /ping, POST /products, POST /orders, PATCH /orders/:id ve DELETE /products/:id.
Hatalar
Kodunuzda error.code'a göre dallanın; error.message yalnızca okumak içindir (Arapça).
unauthorized | Anahtar eksik, bilinmiyor veya iptal edilmiş. |
rate_limited | Limit aşıldı. Retry-After süresini bekleyin. |
invalid_json | Gövde geçerli JSON değil. |
invalid_body | İsteğin yapısı hatalı (ör. products dizisi yok). |
not_found | Bu kimlikle bir öğe yok. |
all_failed | Tüm öğeler başarısız; mesaj ilk nedeni taşır. |
payload_too_large | 4 MB'tan büyük — bölün. |
already_used | Kupon zaten kullanıldı (409). |
expired | Kuponun süresi doldu (410). |
not_redeemed | Geri alınacak kullanım yok (409). |
internal_error | Bizim tarafımızda bir hata. Biraz sonra yeniden deneyin. |
Kısmi başarı
Bazı öğeler başarılı, bazıları başarısız olduğunda yanıt 200'dür ve başarısız her öğenin nedeni results içindedir. failed alanını okumadan 200'ü tam başarı saymayın.
Limitler
- Anahtar başına dakikada 300 istek. Aşılırsa Retry-After ile 429 döner.
- İstek başına 4 MB.
- İstek başına 500 ürün, 100 sipariş, 100 sepet veya 500 müşteri.
Referans
Her uç nokta, istek yapısı ve dört dilde hazır örneklerle.
Alan açıklamaları İngilizcedir; doğrudan API belgesinden üretilir, bu yüzden API'nin kabul ettiğiyle her zaman eşleşir.
GETAnahtarınızı doğrulayın
https://capiagent.com/api/ingest/v1/pingMarkanın adını, para birimini ve anahtar türünü (canlı veya test) döndürür.
curl -X GET https://capiagent.com/api/ingest/v1/ping \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"ok": true,
"mode": "live",
"store": {
"name": "Lumi Store",
"currency": "SAR"
}
}POSTÜrün oluşturun veya güncelleyin
https://capiagent.com/api/ingest/v1/productsİstek başına 500'e kadar; yalnızca id ve name zorunlu. complete: true'yu yalnızca istek tüm kataloğunuzu içeriyorsa gönderin.
| Alan | Tür | Zorunlu | Notlar |
|---|---|---|---|
products | object[] | Evet | |
products[].id | string | Evet | Your own product id. Every request is matched on it — re-sending the same id updates the product, never duplicates it. |
products[].name | string | Evet | Product name as the customer sees it. |
products[].description | string | null | Plain text or HTML. HTML is stripped for the assistant. | |
products[].price | number | string | null | Major units (199.99), as a JSON number (a string is accepted but read by guesswork and warned, `string_amount`). When `salePrice` is lower, this reads as the price before discount. | |
products[].salePrice | number | string | null | The current price when the product is discounted. | |
products[].compareAtPrice | number | string | null | An explicit "was" price, when you keep one separately. | |
products[].costPrice | number | string | null | What the item costs you (cost of goods). Send it — profit, margin and the ads stock/margin rules need it; never shown to customers. Per variant too, when sizes cost differently. | |
products[].currency | string | null | ISO 4217 code. Defaults to the brand's currency. | |
products[].sku | string | null | Stock keeping unit. | |
products[].url | string (uri) | null | Public product page — the link the assistant sends. | |
products[].images | string (uri)[] | Image URLs, main image first. Up to 20. | |
products[].categories | string[] | Category names, broadest first. | |
products[].stock | integer | null | Units available. Decides availability; 0 means out of stock. | |
products[].availability | "in_stock" | "out_of_stock" | null | Used only when `stock` is not sent. Without either, availability is "unknown" and the assistant never claims it is sold out. | |
products[].published | boolean | `false` archives the product immediately (it stays in order history). | |
products[].variants | object[] | Sizes, colours and other options, each with its own id, price and stock. | |
products[].variants[].id | string | Evet | |
products[].variants[].name | string | null | ||
products[].variants[].sku | string | null | ||
products[].variants[].price | number | string | null | ||
products[].variants[].compareAtPrice | number | string | null | ||
products[].variants[].costPrice | number | string | null | ||
products[].variants[].stock | integer | null | ||
complete | boolean | `true` says this request is your whole catalogue: products missing from it are archived. Ignored if any item fails. Use it only when the catalogue fits in one request, and never together with a catalog feed link. |
curl -X POST https://capiagent.com/api/ingest/v1/products \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"id": "SKU-1001",
"name": "Cotton shirt",
"price": 199.99,
"salePrice": 149.99,
"costPrice": 80,
"stock": 12,
"url": "https://mystore.com/p/1001",
"images": [
"https://mystore.com/img/1001.jpg"
],
"categories": [
"Clothing"
],
"variants": [
{
"id": "SKU-1001-M",
"name": "M",
"price": 149.99,
"costPrice": 80,
"stock": 5
}
]
}
]
}'{
"ok": true,
"succeeded": 1,
"failed": 0,
"results": [
{
"id": "SKU-1001",
"ok": true
}
]
}PATCHpatchProduct
https://capiagent.com/api/ingest/v1/products/{id}| Alan | Tür | Zorunlu | Notlar |
|---|---|---|---|
price | number | string | null | ||
salePrice | number | string | null | ||
compareAtPrice | number | string | null | ||
costPrice | number | string | null | ||
stock | integer | null | ||
availability | "in_stock" | "out_of_stock" | null | ||
published | boolean |
curl -X PATCH https://capiagent.com/api/ingest/v1/products/SKU-1001 \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"price": 199.99,
"salePrice": 149.99,
"costPrice": 80,
"stock": 3
}'{
"ok": true,
"succeeded": 1,
"failed": 0,
"results": [
{
"id": "SKU-1001",
"ok": true
}
]
}DELETEÜrünü arşivleyin
https://capiagent.com/api/ingest/v1/products/{id}Ürünü katalogdan çıkarır; sipariş geçmişinde kalır.
curl -X DELETE https://capiagent.com/api/ingest/v1/products/SKU-1001 \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"ok": true,
"succeeded": 1,
"failed": 0,
"results": [
{
"id": "SKU-1001",
"ok": true
}
]
}POSTSipariş oluşturun veya güncelleyin
https://capiagent.com/api/ingest/v1/ordersHer yeni siparişi gönderin. backfill: true eski siparişleri mesaj veya otomasyon tetiklemeden içe aktarır.
| Alan | Tür | Zorunlu | Notlar |
|---|---|---|---|
orders | object[] | Evet | |
orders[].id | string | Evet | Your order number. Re-sending the same id updates the order; its items are replaced in full. |
orders[].status | string | Evet | Order status: pending, processing, shipped, delivered, cancelled or refunded — or your system's own status as is (e.g. awaiting_pickup). An unknown one is kept and the merchant decides once in the dashboard what it means; until then it sends nothing. A change fires order automations and the customer's status message, as on any platform. |
orders[].statusName | string | null | Optional. The status's name as your store's admin shows it (e.g. "Awaiting pickup") — what the merchant sees when deciding. Defaults to the code. | |
orders[].total | number | string | Evet | Order total in major units. |
orders[].currency | string | ISO 4217 code. Defaults to the brand's currency. | |
orders[].placedAt | string | ISO 8601. A date in the future is refused. | |
orders[].customer | object | Who ordered. The phone must be international, digits only (966501234567) — it is the customer's WhatsApp identity and how the assistant finds their orders. | |
orders[].customer.name | string | null | ||
orders[].customer.phone | string | null | ||
orders[].customer.email | string (email) | null | ||
orders[].items | object[] | Order lines. Send each line's `productId` (the id you used on /products) to link it to the catalogue; without it the name is matched exactly. | |
orders[].items[].productId | string | null | ||
orders[].items[].name | string | Evet | |
orders[].items[].quantity | integer | Evet | |
orders[].items[].price | number | string | Evet | |
orders[].subtotal | number | string | null | ||
orders[].discount | number | string | null | ||
orders[].tax | number | string | null | ||
orders[].shipping | number | string | null | ||
orders[].refunded | number | string | null | ||
orders[].shipment | object | Carrier and tracking — the assistant answers "where is my order?" from these fields. | |
orders[].shipment.carrier | string | null | ||
orders[].shipment.trackingNumber | string | null | ||
orders[].shipment.trackingUrl | string (uri) | null | ||
orders[].shipment.shippedAt | string | null | ||
orders[].shipment.deliveredAt | string | null | ||
orders[].shipment.estimatedDeliveryAt | string | null | ||
orders[].paymentMethod | string | null | `cod` (or `cash_on_delivery`) enables cash-on-delivery confirmation over WhatsApp for this order. | |
orders[].paymentUrl | string (uri) | null | An https link where the customer pays this same order online with your prepaid discount. Sent after they confirm a COD order, when that offer is on. | |
orders[].couponCode | string | null | ||
orders[].backfill | boolean | `true` stores the order without firing automations, notifications or messages — for importing history. | |
orders[].tracking | object | Optional. The buyer's browser at checkout — landingUrl (with the ad's fbclid/gclid/ttclid/ScCid), clickIds, the pixel cookies fbp/fbc, userAgent and ip — so the ad platforms can count a sale from an ad that never went through the chat. The IP is shared only when the merchant turned on enhanced matching. If the store has a cookie consent banner, send it only when the buyer accepted marketing cookies — the condition is the consent, not the pixel loading (a blocked pixel still sends, without fbp/fbc). | |
orders[].tracking.landingUrl | string (uri) | null | ||
orders[].tracking.userAgent | string | null | ||
orders[].tracking.ip | string | null | ||
orders[].tracking.fbp | string | null | ||
orders[].tracking.fbc | string | null | ||
orders[].tracking.clickIds | object | ||
orders[].tracking.clickIds.fbclid | string | ||
orders[].tracking.clickIds.gclid | string | ||
orders[].tracking.clickIds.gbraid | string | ||
orders[].tracking.clickIds.wbraid | string | ||
orders[].tracking.clickIds.ttclid | string | ||
orders[].tracking.clickIds.ScCid | string |
curl -X POST https://capiagent.com/api/ingest/v1/orders \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"orders": [
{
"id": "10045",
"status": "processing",
"total": 349.98,
"currency": "SAR",
"placedAt": "2026-10-06T09:00:00Z",
"customer": {
"name": "Sara",
"phone": "966501234567",
"email": "sara@example.com"
},
"items": [
{
"productId": "SKU-1001",
"name": "Cotton shirt",
"quantity": 2,
"price": 174.99
}
],
"couponCode": "CAPI-7K3M9",
"paymentMethod": "cod",
"paymentUrl": "https://mystore.com/pay/10045"
}
]
}'{
"ok": true,
"succeeded": 1,
"failed": 0,
"results": [
{
"id": "10045",
"ok": true
}
]
}PATCHSipariş durumunu veya kargoyu güncelleyin
https://capiagent.com/api/ingest/v1/orders/{id}Yalnızca gönderdiğiniz alanlar değişir. Durum değişikliği, satıcının ayarlarına göre müşteriye sipariş durumu mesajı gönderir.
| Alan | Tür | Zorunlu | Notlar |
|---|---|---|---|
status | string | New status — one of ours or your system's own, as on POST /orders. Fires order.status_changed like any platform. | |
statusName | string | null | Optional. The status's name in your store's admin. | |
shipment | object | Only the fields you send change; a status-only patch keeps the stored tracking number. | |
shipment.carrier | string | null | ||
shipment.trackingNumber | string | null | ||
shipment.trackingUrl | string (uri) | null | ||
shipment.shippedAt | string | null | ||
shipment.deliveredAt | string | null | ||
shipment.estimatedDeliveryAt | string | null | ||
refunded | number | string | null | Amount refunded so far, in major units. | |
paymentMethod | string | null | The customer paid online after all (e.g. `card`): the order is no longer cash on delivery. | |
paymentUrl | string (uri) | null | Replace the order's online payment link. |
curl -X PATCH https://capiagent.com/api/ingest/v1/orders/10045 \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"status": "shipped",
"shipment": {
"carrier": "SMSA",
"trackingNumber": "290012345678",
"trackingUrl": "https://track.example.com/290012345678"
}
}'{
"ok": true,
"succeeded": 1,
"failed": 0,
"results": [
{
"id": "10045",
"ok": true
}
]
}POSTSepetleri gönderin
https://capiagent.com/api/ingest/v1/cartsSepet her değiştiğinde tamamını gönderin. Boş sepet, kapandığı anlamına gelir.
| Alan | Tür | Zorunlu | Notlar |
|---|---|---|---|
carts | object[] | Evet | |
carts[].id | string | Evet | Your cart or session id. Re-sending the same id updates the cart. |
carts[].currency | string | null | ||
carts[].total | number | string | null | ||
carts[].items | object[] | The whole cart every time, never a delta. An empty array closes the cart (bought or cleared). | |
carts[].items[].productId | string | null | The same id you send on POST /products — how the line links to the catalogue. | |
carts[].items[].name | string | null | ||
carts[].items[].quantity | integer | null | ||
carts[].items[].price | number | string | null | ||
carts[].customer | object | Contact for the recovery message. A phone is not marketing consent; sending is governed by the customer's subscription. | |
carts[].customer.name | string | null | ||
carts[].customer.phone | string | null | ||
carts[].customer.email | string (email) | null | ||
carts[].checkoutUrl | string (uri) | null | Your "resume this checkout" link, sent as-is in the recovery message. https only. | |
carts[].updatedAt | string | null | When the shopper last touched the cart (ISO 8601). Reminder timing is computed from it; a future value is clamped to now. |
curl -X POST https://capiagent.com/api/ingest/v1/carts \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"carts": [
{
"id": "cart-7f3a",
"currency": "SAR",
"total": 199.99,
"items": [
{
"productId": "SKU-1001",
"name": "Cotton shirt",
"quantity": 1,
"price": 199.99
}
],
"customer": {
"name": "Sara",
"phone": "966501234567"
},
"checkoutUrl": "https://mystore.com/checkout/cart-7f3a",
"updatedAt": "2026-10-06T08:40:00Z"
}
]
}'{
"ok": true,
"succeeded": 1,
"failed": 0,
"results": [
{
"id": "cart-7f3a",
"ok": true
}
]
}POSTMüşterileri gönderin
https://capiagent.com/api/ingest/v1/customersHenüz sipariş vermemiş olabilecek kayıtlı müşteriler ve sisteminizden bize senkronize edilen abonelik iptalleri.
| Alan | Tür | Zorunlu | Notlar |
|---|---|---|---|
customers | object[] | Evet | |
customers[].id | string | Evet | Your own customer id — echoed back in the results. Customers are matched on phone, then email. |
customers[].name | string | null | ||
customers[].phone | string | null | International, digits only (966501234567). A local number is refused with a warning. | |
customers[].email | string (email) | null | Used as the identity when there is no phone. | |
customers[].country | string | null | ||
customers[].city | string | null | ||
customers[].acceptsMarketing | boolean | null | The customer's marketing choice in your system. `false` unsubscribes them at once. `true` confirms a subscription but never re-subscribes a customer who opted out themselves — a "STOP" on WhatsApp stands. |
curl -X POST https://capiagent.com/api/ingest/v1/customers \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"customers": [
{
"id": "U-501",
"name": "Sara",
"phone": "966501234567",
"email": "sara@example.com",
"city": "Riyadh",
"acceptsMarketing": true
},
{
"id": "U-502",
"email": "omar@example.com",
"acceptsMarketing": false
}
]
}'{
"ok": true,
"succeeded": 2,
"failed": 0,
"results": [
{
"id": "U-501",
"ok": true
},
{
"id": "U-502",
"ok": true
}
]
}GETcheckCoupon
https://capiagent.com/api/ingest/v1/coupons/{id}curl -X GET https://capiagent.com/api/ingest/v1/coupons/CAPI-7K3M9 \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"ok": true,
"valid": true,
"coupon": {
"code": "CAPI-7K3M9",
"discountType": "percent",
"percent": 10,
"expiresAt": "2026-10-12T18:00:00.000Z",
"usageLimit": 1
}
}POSTredeemCoupon
https://capiagent.com/api/ingest/v1/coupons/{id}/redeem| Alan | Tür | Zorunlu | Notlar |
|---|---|---|---|
orderId | string | Your order id (a string; a number is accepted; longer than 120 characters it is cut to 120). Send it: a retry for the same order (the first answer was lost) is answered 200 with `replayed: true`, not `already_used`. The body is optional — a missing or malformed body is ignored, it never blocks the redeem. |
curl -X POST https://capiagent.com/api/ingest/v1/coupons/CAPI-7K3M9/redeem \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"orderId": "10045"
}'{
"ok": true,
"redeemed": true,
"coupon": {
"code": "CAPI-7K3M9",
"discountType": "percent",
"percent": 10,
"expiresAt": "2026-10-12T18:00:00.000Z",
"usageLimit": 1,
"redeemedAt": "2026-10-06T10:00:00.000Z"
}
}DELETEreleaseCoupon
https://capiagent.com/api/ingest/v1/coupons/{id}/redeemcurl -X DELETE https://capiagent.com/api/ingest/v1/coupons/CAPI-7K3M9/redeem \
-H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"{
"ok": true,
"released": true
}Webhook'lar
Sisteminizdeki tek bir adres tüm olayları alır. Her biri imzalı bir POST'tur; 2xx ile yanıt verin.
Kurulum
- Paneldeki Özel mağaza bölümünde adresinizi kaydedin (yalnızca https).
- Bir kez gösterilen imza anahtarını (capi_whsec_…) sisteminize kopyalayın.
- "Test ping gönder"e basın ve adresinizin imzayı doğrulayıp 2xx döndüğünden emin olun.
Olaylar
coupon.create | Asistanın bir müşteri için indirim koduna ihtiyacı var. Oluşturun ve 10 saniye içinde yanıt verin. Yeniden denenmez. |
order.cod_confirmed | Müşteri kapıda ödemeli siparişini WhatsApp'ta onayladı. |
order.cod_cancelled | Müşteri kapıda ödemeli siparişini iptal etti. cancelRequested true ise siparişi iptal edip stoğu geri ekleyin. |
customer.unsubscribed | Müşteri bizde pazarlama aboneliğinden çıktı. Sizin listenizde de çıkarın. |
ping | Paneldeki düğmeden gelen test olayı. |
POST https://your-store.com/capi/webhook
Content-Type: application/json
X-Capi-Event: order.cod_cancelled
X-Capi-Api-Version: v1
X-Capi-Event-Id: evt_cm9x2k1a80001
X-Capi-Delivery-Attempt: 1
X-Capi-Signature: t=1759740720,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"id": "evt_cm9x2k1a80001",
"event": "order.cod_cancelled",
"apiVersion": "v1",
"createdAt": "2026-10-06T09:12:00.000Z",
"data": {
"orderId": "10045",
"outcome": "cancelled",
"cancelRequested": true,
"note": "Capi Agent: the customer cancelled this cash-on-delivery order on WhatsApp.",
"channel": "whatsapp",
"respondedAt": "2026-10-06T09:12:00.000Z"
}
}İmzayı doğrulama
X-Capi-Signature, t (zaman damgası) ve v1 (gizli anahtarınızla t.<ham gövde> metninin HMAC-SHA256'sı) taşır. Kendiniz hesaplayın, sabit zamanlı karşılaştırın ve 5 dakikadan eski t'yi reddedin. Başlık birden fazla v1 (sır değişimi sırasında) ve bilmediğiniz anahtarlar taşıyabilir: herhangi bir v1 eşleşirse geçerlidir; gerisini yok sayın. Gövdeyi geldiği gibi ham haliyle kullanın, nesneye çevrilmiş halini değil.
Yeniden denemeler
coupon.create ve ping dışındaki her olay aynı id ile bir buçuk gün içinde 8 kereye kadar yeniden denenir (1 dk, 5 dk, 30 dk, 2 sa, 6, 12, 24 sa). İşlediğiniz kimlikleri saklayın ve tekrarları atlayın. Denemeler bitince satıcıya bildirim gider ve olay günlüğünden yeniden gönderebilir.
Eksiksiz bir alıcı
Doğrula, tekrarı atla, uygula, 2xx döndür — bu sırayla.
import crypto from "node:crypto";
// Use the RAW request body — not a re-serialised object.
// The header may carry several v1= (while a secret is being rotated) and keys
// you do not know yet: it is valid when ANY v1 matches. Ignore the rest.
export function verifyCapiSignature(rawBody, header, secret) {
const parts = header.split(",").map((p) => p.trim().split("="));
const t = Number(parts.find(([key]) => key === "t")?.[1]);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes
const expected = Buffer.from(crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"));
return parts.some(
([key, value]) => key === "v1" && Buffer.byteLength(value ?? "") === expected.length && crypto.timingSafeEqual(expected, Buffer.from(value)),
);
}app.post("/capi/webhook", express.raw({ type: "application/json" }), async (req, res) => {
const raw = req.body.toString("utf8");
if (!verifyCapiSignature(raw, req.get("X-Capi-Signature") ?? "", process.env.CAPI_WEBHOOK_SECRET)) {
return res.status(401).end();
}
const event = JSON.parse(raw);
if (await alreadyHandled(event.id)) return res.status(200).end(); // retries keep the same id
switch (event.event) {
case "coupon.create": {
const coupon = await createCoupon(event.data); // answer within 10 seconds
await markHandled(event.id);
return res.json({ code: coupon.code, id: coupon.id });
}
case "order.cod_cancelled":
if (event.data.cancelRequested) await cancelOrder(event.data.orderId);
else await addOrderNote(event.data.orderId, event.data.note);
break;
case "order.cod_confirmed":
await markConfirmed(event.data.orderId);
break;
case "customer.unsubscribed":
await unsubscribe(event.data.phone, event.data.email);
break;
}
await markHandled(event.id);
res.status(200).end();
});Widget ve tarayıcı olayları
Bu bölüm sunucunuzda değil, mağaza sayfalarının kendisinde (tarayıcı JavaScript'i) çalışır. Sohbet asistanını gösterir ve ziyaretçinin yaptıklarını — ürün görüntüleme, sepete ekleme, ödemeye başlama — aynı ziyaretçiye bağlar; canlı sohbet kampanyaları ve satış atfı buna dayanır.
Widget kurulumu
Satırı, ödeme ve teşekkür sayfası dahil tüm sayfaların ortak şablonunda body etiketinin bitiminden önce yapıştırın. STORE_ID yerine panodaki mağaza kimliğinizi yazın. Widget, «Şimdi yayınla» sonrasında ziyaretçilere görünür.
<script src="https://capiagent.com/widget.js" data-store-id="STORE_ID" async></script>Satın alma hunisi olayları — zorunlu
PRODUCT_VIEW | Her ürün sayfası yüklendiğinde ve SPA mağazada başka bir ürüne her geçişte. |
ADD_TO_CART | Her başarılı sepete eklemeden sonra, hangi düğmeden olursa olsun: ürün sayfası, listeler, hızlı görünüm, «hemen al». |
CHECKOUT_STARTED | Ödeme sayfası açıldığında, ardından sipariş oluşturulur oluşturulmaz orderExternalId ile bir kez daha. |
Bozulmaması gereken kurallar
- productExternalId, POST /products içindeki ürün id'sinin aynısıdır — sku veya varyant id'si değil.
- orderExternalId, POST /orders içindeki sipariş id'sinin aynısıdır — sepet id'si değil. Sipariş canlı sohbet kampanyasına bununla atfedilir.
- Kodunuz widget yüklenmeden çalışabiliyorsa örnekteki gibi AISA.q.push kullanın; olay saklanır ve yüklemede gönderilir.
- JavaScript'siz alternatif: ürün sayfasında widget etiketine data-capi-product-id, ödeme sayfasında data-capi-checkout="1".
- Satın alma olayı tarayıcıdan kabul edilmez: siparişler ve tutarlar yalnızca POST /orders ile gelir.
- Satıcı widget için izinli alan adları belirlediyse tüm mağaza alan adlarını ekleyin, yoksa olaylar reddedilir (403).
// Product page — on load (and on every SPA navigation to a product)
AISA.track("PRODUCT_VIEW", { productExternalId: "SKU-1001" });
// After every successful add to cart, from any button
AISA.track("ADD_TO_CART", { productExternalId: "SKU-1001", quantity: 1 });
// Checkout page opened
AISA.track("CHECKOUT_STARTED", {});
// Thank-you page — the order id you send to POST /orders
AISA.track("CHECKOUT_STARTED", { orderExternalId: "10045" });
// Safe before widget.js has loaded (queued, sent on load):
(window.AISA = window.AISA || {}).q = window.AISA.q || [];
window.AISA.q.push(["ADD_TO_CART", { productExternalId: "SKU-1001", quantity: 1 }]);Giriş yapmış müşteri — AISA.identify
Müşteri giriş yaptığı sürece her sayfada, uluslararası telefonu veya e-postasıyla çağırın (ad isteğe bağlı). Widget bunu bir kez, sonra yalnızca bilgiler değişince gönderir. Böylece müşteriden sohbet öncesi formda bilgi istenmez; sohbetleri, siparişleri ve kampanya atfı tek müşteride birleşir. Bu çağrı kimseyi pazarlamaya abone etmez: onay POST /customers içindeki acceptsMarketing ile gelir.
Kodlama asistanınıza hazır kılavuz
Capi panosu ← Özel mağaza'daki «Geliştirici kılavuzunu kopyala» düğmesi, mağaza kimliğinizle eksiksiz bir kılavuz kopyalar: her özelliğin neye ihtiyaç duyduğu, zorunlu kontrol listesi, tüm alanlar ve entegrasyonu yapıp madde madde rapor veren, Claude veya ChatGPT için hazır bir istek.
// Every page, while the customer is logged in (phone or email required)
AISA.identify({ name: "Sara", phone: "966501234567", email: "sara@example.com" });
// Or, before widget.js has loaded:
(window.AISA = window.AISA || {}).q = window.AISA.q || [];
window.AISA.q.push(["identify", { name: "Sara", phone: "966501234567", email: "sara@example.com" }]);
// Signed (recommended): add "hash", computed on YOUR server — see SIGNED_IDENTIFY_SAMPLE.
// Then the same customer keeps one chat history in every browser.
AISA.identify({ name: "Sara", email: "sara@example.com", hash: "<?= $capiHash ?>" });Destek
Bu sayfanın yanıtlamadığı bir soru ya da nedenini göremediğiniz bir ret mi var? İsteği ve yanıtın tamamını bize yazın.
Destekle iletişime geçin