Geliştiriciler için

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.

OpenAPI belgesini indirAnahtar oluştur

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/v1
Kod dili
Bu sayfada

Hızlı başlangıç

  1. 1Capi Agent panelinde Entegrasyonlar → Özel mağaza'yı açın ve önce bir test anahtarı oluşturun.
  2. 2Anahtarı GET /ping ile doğrulayın.
  3. 3Ürünleri POST /products ile, her siparişi POST /orders ile, durum değişikliklerini PATCH /orders/{id} ile gönderin.
  4. 4Test anahtarıyla tüm istekler başarılı olunca canlı bir anahtar oluşturup değiştirin.
  5. 5İsteğe bağlı: kuponları, sipariş onaylarını ve abonelik iptallerini almak için bir olay adresi ekleyin.
  6. 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ü).
İstek
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ı.

  1. 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.
  2. 15.10.2026
    • AISA.identify içinde imzalı müşteri kimliği (hash): konuşmalar cihazlar arasında birleşir.
  3. 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.
  4. 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ı.
  5. 05.10.2026
    • Terk edilmiş sepetler için POST /carts.
    • coupon.create olayı.
  6. 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).

unauthorizedAnahtar eksik, bilinmiyor veya iptal edilmiş.
rate_limitedLimit aşıldı. Retry-After süresini bekleyin.
invalid_jsonGövde geçerli JSON değil.
invalid_bodyİsteğin yapısı hatalı (ör. products dizisi yok).
not_foundBu kimlikle bir öğe yok.
all_failedTüm öğeler başarısız; mesaj ilk nedeni taşır.
payload_too_large4 MB'tan büyük — bölün.
already_usedKupon zaten kullanıldı (409).
expiredKuponun süresi doldu (410).
not_redeemedGeri alınacak kullanım yok (409).
internal_errorBizim 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

GEThttps://capiagent.com/api/ingest/v1/ping

Markanın adını, para birimini ve anahtar türünü (canlı veya test) döndürür.

İstek
curl -X GET https://capiagent.com/api/ingest/v1/ping \
  -H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Yanıt
JSON
{
  "ok": true,
  "mode": "live",
  "store": {
    "name": "Lumi Store",
    "currency": "SAR"
  }
}

POSTÜrün oluşturun veya güncelleyin

POSThttps://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.

AlanTürZorunluNotlar
productsobject[]Evet
products[].idstringEvetYour own product id. Every request is matched on it — re-sending the same id updates the product, never duplicates it.
products[].namestringEvetProduct name as the customer sees it.
products[].descriptionstring | nullPlain text or HTML. HTML is stripped for the assistant.
products[].pricenumber | string | nullMajor 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[].salePricenumber | string | nullThe current price when the product is discounted.
products[].compareAtPricenumber | string | nullAn explicit "was" price, when you keep one separately.
products[].costPricenumber | string | nullWhat 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[].currencystring | nullISO 4217 code. Defaults to the brand's currency.
products[].skustring | nullStock keeping unit.
products[].urlstring (uri) | nullPublic product page — the link the assistant sends.
products[].imagesstring (uri)[]Image URLs, main image first. Up to 20.
products[].categoriesstring[]Category names, broadest first.
products[].stockinteger | nullUnits available. Decides availability; 0 means out of stock.
products[].availability"in_stock" | "out_of_stock" | nullUsed only when `stock` is not sent. Without either, availability is "unknown" and the assistant never claims it is sold out.
products[].publishedboolean`false` archives the product immediately (it stays in order history).
products[].variantsobject[]Sizes, colours and other options, each with its own id, price and stock.
products[].variants[].idstringEvet
products[].variants[].namestring | null
products[].variants[].skustring | null
products[].variants[].pricenumber | string | null
products[].variants[].compareAtPricenumber | string | null
products[].variants[].costPricenumber | string | null
products[].variants[].stockinteger | null
completeboolean`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.
İstek
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
        }
      ]
    }
  ]
}'
Yanıt
JSON
{
  "ok": true,
  "succeeded": 1,
  "failed": 0,
  "results": [
    {
      "id": "SKU-1001",
      "ok": true
    }
  ]
}

PATCHpatchProduct

PATCHhttps://capiagent.com/api/ingest/v1/products/{id}
AlanTürZorunluNotlar
pricenumber | string | null
salePricenumber | string | null
compareAtPricenumber | string | null
costPricenumber | string | null
stockinteger | null
availability"in_stock" | "out_of_stock" | null
publishedboolean
İstek
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
}'
Yanıt
JSON
{
  "ok": true,
  "succeeded": 1,
  "failed": 0,
  "results": [
    {
      "id": "SKU-1001",
      "ok": true
    }
  ]
}

DELETEÜrünü arşivleyin

DELETEhttps://capiagent.com/api/ingest/v1/products/{id}

Ürünü katalogdan çıkarır; sipariş geçmişinde kalır.

İstek
curl -X DELETE https://capiagent.com/api/ingest/v1/products/SKU-1001 \
  -H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Yanıt
JSON
{
  "ok": true,
  "succeeded": 1,
  "failed": 0,
  "results": [
    {
      "id": "SKU-1001",
      "ok": true
    }
  ]
}

POSTSipariş oluşturun veya güncelleyin

POSThttps://capiagent.com/api/ingest/v1/orders

Her yeni siparişi gönderin. backfill: true eski siparişleri mesaj veya otomasyon tetiklemeden içe aktarır.

AlanTürZorunluNotlar
ordersobject[]Evet
orders[].idstringEvetYour order number. Re-sending the same id updates the order; its items are replaced in full.
orders[].statusstringEvetOrder 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[].statusNamestring | nullOptional. 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[].totalnumber | stringEvetOrder total in major units.
orders[].currencystringISO 4217 code. Defaults to the brand's currency.
orders[].placedAtstringISO 8601. A date in the future is refused.
orders[].customerobjectWho 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.namestring | null
orders[].customer.phonestring | null
orders[].customer.emailstring (email) | null
orders[].itemsobject[]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[].productIdstring | null
orders[].items[].namestringEvet
orders[].items[].quantityintegerEvet
orders[].items[].pricenumber | stringEvet
orders[].subtotalnumber | string | null
orders[].discountnumber | string | null
orders[].taxnumber | string | null
orders[].shippingnumber | string | null
orders[].refundednumber | string | null
orders[].shipmentobjectCarrier and tracking — the assistant answers "where is my order?" from these fields.
orders[].shipment.carrierstring | null
orders[].shipment.trackingNumberstring | null
orders[].shipment.trackingUrlstring (uri) | null
orders[].shipment.shippedAtstring | null
orders[].shipment.deliveredAtstring | null
orders[].shipment.estimatedDeliveryAtstring | null
orders[].paymentMethodstring | null`cod` (or `cash_on_delivery`) enables cash-on-delivery confirmation over WhatsApp for this order.
orders[].paymentUrlstring (uri) | nullAn 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[].couponCodestring | null
orders[].backfillboolean`true` stores the order without firing automations, notifications or messages — for importing history.
orders[].trackingobjectOptional. 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.landingUrlstring (uri) | null
orders[].tracking.userAgentstring | null
orders[].tracking.ipstring | null
orders[].tracking.fbpstring | null
orders[].tracking.fbcstring | null
orders[].tracking.clickIdsobject
orders[].tracking.clickIds.fbclidstring
orders[].tracking.clickIds.gclidstring
orders[].tracking.clickIds.gbraidstring
orders[].tracking.clickIds.wbraidstring
orders[].tracking.clickIds.ttclidstring
orders[].tracking.clickIds.ScCidstring
İstek
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"
    }
  ]
}'
Yanıt
JSON
{
  "ok": true,
  "succeeded": 1,
  "failed": 0,
  "results": [
    {
      "id": "10045",
      "ok": true
    }
  ]
}

PATCHSipariş durumunu veya kargoyu güncelleyin

PATCHhttps://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.

AlanTürZorunluNotlar
statusstringNew status — one of ours or your system's own, as on POST /orders. Fires order.status_changed like any platform.
statusNamestring | nullOptional. The status's name in your store's admin.
shipmentobjectOnly the fields you send change; a status-only patch keeps the stored tracking number.
shipment.carrierstring | null
shipment.trackingNumberstring | null
shipment.trackingUrlstring (uri) | null
shipment.shippedAtstring | null
shipment.deliveredAtstring | null
shipment.estimatedDeliveryAtstring | null
refundednumber | string | nullAmount refunded so far, in major units.
paymentMethodstring | nullThe customer paid online after all (e.g. `card`): the order is no longer cash on delivery.
paymentUrlstring (uri) | nullReplace the order's online payment link.
İstek
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"
  }
}'
Yanıt
JSON
{
  "ok": true,
  "succeeded": 1,
  "failed": 0,
  "results": [
    {
      "id": "10045",
      "ok": true
    }
  ]
}

POSTSepetleri gönderin

POSThttps://capiagent.com/api/ingest/v1/carts

Sepet her değiştiğinde tamamını gönderin. Boş sepet, kapandığı anlamına gelir.

AlanTürZorunluNotlar
cartsobject[]Evet
carts[].idstringEvetYour cart or session id. Re-sending the same id updates the cart.
carts[].currencystring | null
carts[].totalnumber | string | null
carts[].itemsobject[]The whole cart every time, never a delta. An empty array closes the cart (bought or cleared).
carts[].items[].productIdstring | nullThe same id you send on POST /products — how the line links to the catalogue.
carts[].items[].namestring | null
carts[].items[].quantityinteger | null
carts[].items[].pricenumber | string | null
carts[].customerobjectContact for the recovery message. A phone is not marketing consent; sending is governed by the customer's subscription.
carts[].customer.namestring | null
carts[].customer.phonestring | null
carts[].customer.emailstring (email) | null
carts[].checkoutUrlstring (uri) | nullYour "resume this checkout" link, sent as-is in the recovery message. https only.
carts[].updatedAtstring | nullWhen the shopper last touched the cart (ISO 8601). Reminder timing is computed from it; a future value is clamped to now.
İstek
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"
    }
  ]
}'
Yanıt
JSON
{
  "ok": true,
  "succeeded": 1,
  "failed": 0,
  "results": [
    {
      "id": "cart-7f3a",
      "ok": true
    }
  ]
}

POSTMüşterileri gönderin

POSThttps://capiagent.com/api/ingest/v1/customers

Henüz sipariş vermemiş olabilecek kayıtlı müşteriler ve sisteminizden bize senkronize edilen abonelik iptalleri.

AlanTürZorunluNotlar
customersobject[]Evet
customers[].idstringEvetYour own customer id — echoed back in the results. Customers are matched on phone, then email.
customers[].namestring | null
customers[].phonestring | nullInternational, digits only (966501234567). A local number is refused with a warning.
customers[].emailstring (email) | nullUsed as the identity when there is no phone.
customers[].countrystring | null
customers[].citystring | null
customers[].acceptsMarketingboolean | nullThe 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.
İstek
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
    }
  ]
}'
Yanıt
JSON
{
  "ok": true,
  "succeeded": 2,
  "failed": 0,
  "results": [
    {
      "id": "U-501",
      "ok": true
    },
    {
      "id": "U-502",
      "ok": true
    }
  ]
}

GETcheckCoupon

GEThttps://capiagent.com/api/ingest/v1/coupons/{id}
İstek
curl -X GET https://capiagent.com/api/ingest/v1/coupons/CAPI-7K3M9 \
  -H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Yanıt
JSON
{
  "ok": true,
  "valid": true,
  "coupon": {
    "code": "CAPI-7K3M9",
    "discountType": "percent",
    "percent": 10,
    "expiresAt": "2026-10-12T18:00:00.000Z",
    "usageLimit": 1
  }
}

POSTredeemCoupon

POSThttps://capiagent.com/api/ingest/v1/coupons/{id}/redeem
AlanTürZorunluNotlar
orderIdstringYour 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.
İstek
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"
}'
Yanıt
JSON
{
  "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

DELETEhttps://capiagent.com/api/ingest/v1/coupons/{id}/redeem
İstek
curl -X DELETE https://capiagent.com/api/ingest/v1/coupons/CAPI-7K3M9/redeem \
  -H "Authorization: Bearer capi_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Yanıt
JSON
{
  "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

  1. Paneldeki Özel mağaza bölümünde adresinizi kaydedin (yalnızca https).
  2. Bir kez gösterilen imza anahtarını (capi_whsec_…) sisteminize kopyalayın.
  3. "Test ping gönder"e basın ve adresinizin imzayı doğrulayıp 2xx döndüğünden emin olun.

Olaylar

coupon.createAsistanın bir müşteri için indirim koduna ihtiyacı var. Oluşturun ve 10 saniye içinde yanıt verin. Yeniden denenmez.
order.cod_confirmedMüşteri kapıda ödemeli siparişini WhatsApp'ta onayladı.
order.cod_cancelledMüşteri kapıda ödemeli siparişini iptal etti. cancelRequested true ise siparişi iptal edip stoğu geri ekleyin.
customer.unsubscribedMüşteri bizde pazarlama aboneliğinden çıktı. Sizin listenizde de çıkarın.
pingPaneldeki düğmeden gelen test olayı.
Olay yapısı
HTTP
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.

İmzayı doğrulama
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)),
  );
}
Eksiksiz bir alıcı
Node.js (Express)
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.

Widget kurulumu
HTML
<script src="https://capiagent.com/widget.js" data-store-id="STORE_ID" async></script>

Satın alma hunisi olayları — zorunlu

PRODUCT_VIEWHer ürün sayfası yüklendiğinde ve SPA mağazada başka bir ürüne her geçişte.
ADD_TO_CARTHer 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).
Satın alma hunisi olayları — zorunlu
JavaScript
// 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.

Giriş yapmış müşteri — AISA.identify
JavaScript
// 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