Entegrasyon kılavuzu
ClickFix'i kendi ürününüze baştan sona bağlayın; kimlik doğrulama, dış kimlik, özel alanlar, şirket, olaylar, API kanalı, webhook'lar, sistem bağlama ve mobil webview. Her adımda widget, Node.js, Go, PHP ve curl örneği.
Bu sayfa tek entegrasyon kılavuzudur: panelde Kurulum sihirbazının izlediği sırayla, kendi ürününüzü ClickFix'e bağlamak için yazmanız gereken her şeyi anlatır. Her adımda beş örnek vardır: tarayıcıdaki widget (JavaScript), Node.js (@getclickfix/node), Go (sdks/go modülü), PHP (clickfix/sdk) ve curl. Örnekler repodaki sdks/examples klasöründen gelir ve her sürümde çalıştırılarak doğrulanır; burada gördüğünüz kod, testten geçen koddur.
Başlamadan önce
| Gereken | Nereden | Nerede kullanılır |
|---|---|---|
API anahtarı (cf_sk_…) | Organizasyon → API ve otomasyonlar → API anahtarları | Sunucunuzdan yapılan tüm çağrılar (Authorization: Bearer) |
| Widget kimlik anahtarı | Web widget → Güvenlik → Kimlik doğrulama | user_hash hesaplamak için (kullanıcı + firma kimliği), yalnız sunucuda |
Widget kimliği (WIDGET_ID) | Web widget → Kurulum | widget.js ve mobil webview adresi |
| API kanalı kimliği ve imza anahtarı | Kanallar → Kanal ekle → API | Yalnız kendi sohbet uygulamanızı kanal olarak bağlıyorsanız |
Temel adres https://api.getclickfix.com (sonuna /v1 eklemeyin). Yazma isteklerinde Idempotency-Key zorunludur. Anahtarlar tarayıcıya ve mobil uygulamaya girmez. Yanıt zarfı, hata kodları ve hız sınırları: API.
Paketler
| Paket | Sürüm | Kurulum |
|---|---|---|
widget.js (SDK 2.1) | https://getclickfix.com/widget.js, tipler https://getclickfix.com/widget.d.ts | <script src="https://getclickfix.com/widget.js" data-widget="WIDGET_ID" async></script> |
@getclickfix/node | 0.2.1 | npm install @getclickfix/node (Node 22+) |
@getclickfix/visitor-core | 0.1.1 | npm install @getclickfix/visitor-core (React Native, Node 22+, tarayıcı; DOM'suz) |
@getclickfix/react-widget | 0.1.2 | npm install @getclickfix/react-widget (React 18+) |
| Go modülü | go/v0.1.0 | go get github.com/ClickSolveMainTR/clickfix-sdks/go@v0.1.0 |
clickfix/sdk | 0.1.0 | composer require clickfix/sdk guzzlehttp/guzzle (PHP 8.1+, herhangi bir PSR-18 istemci) |
clickfix/laravel | 0.1.0 | composer require clickfix/laravel (Laravel 10-13, PHP 8.1+); bkz. Laravel paketi |
Değişiklik günlükleri her paketin CHANGELOG.md dosyasındadır. 0.x sürümlerinde küçük sürüm kırıcı değişiklik taşıyabilir; günlükte Kırıcı başlığıyla yazılır. Örnekleri kendi ortamınızda çalıştırmak için ortam değişkenleri: CLICKFIX_BASE_URL, CLICKFIX_API_KEY, CLICKFIX_IDENTITY_SECRET, CLICKFIX_CHANNEL_INSTANCE, CLICKFIX_CHANNEL_SECRET.
Doğru kurulum sırası
Paneldeki Kurulum sihirbazı beş adımdır; bu kılavuzun bölümleri aynı sırayı izler. Her adımı önce panelde açın, sonra kodunu yazın.
| Sihirbaz adımı | Bu kılavuzda | Panelde |
|---|---|---|
| 1. Müşteriyi tanıt | Kimlik doğrulama, dış kimlik, özel alanlar, şirket, olaylar | Web widget → Güvenlik; Ayarlar → Özel alanlar |
| 2. Sistemini bağla | Sistem bağlama | Entegrasyonlar → Sistemini bağla |
| 3. AI'a öğret | Sonraki adımlar | AI → Prosedürler |
| 4. Bilgi bankası | Sonraki adımlar | Bilgi bankası |
| 5. Otomasyon ve SLA | Webhook'lar, sonraki adımlar | Otomasyon, SLA |
API kanalı ve mobil webview kanal kurulumudur; müşteriyi tanıttıktan sonra, sistem bağlamadan önce ya da sonra yapılabilir.
1. Kimlik doğrulama: user_hash
Giriş yapmış kullanıcıyı widget'a kanıtlı biçimde tanıtmanın tek yolu user_hash değeridir:
# kullanıcı bir firmaya bağlıysa (identify'da company.id gönderilir): iki kimlik birlikte imzalanır
user_hash = hex(HMAC-SHA256(widget_kimlik_anahtari, user_id + "\n" + company_id))
# firma yoksa: yalnız kullanıcı kimliği
user_hash = hex(HMAC-SHA256(widget_kimlik_anahtari, user_id))user_id kullanıcının sizin sisteminizdeki kimliğidir ve bir sonraki bölümdeki dış kimliğin kendisidir; company_id firmasının sizin sisteminizdeki kimliğidir ve identify çağrısına company: { id } olarak gider. Değer yalnız sunucunuzda hesaplanır; sitenize ve mobil uygulamanıza anahtar değil, hazır user_hash gider. Geçerli user_hash ile doğrulanan kişi panelde Doğrulanmış rozeti alır; anonim geçmişi aynı kişiye taşınır. Firma kimliği imzalıysa kişi o firmaya imzalı değerle bağlanır, firma da panelde Doğrulanmış rozeti alır ve doğrulanmış bir kullanıcı başka bir firmayı iddia edemez; identify olayı company_verified: true döner. Reddedilen identify çağrısında widget çalışmaya devam eder ama kullanıcı anonim kalır ve error olayı WIDGET_IDENTITY_DISABLED (anahtar üretilmemiş) ya da WIDGET_IDENTITY_INVALID (eşleşmiyor; başka bir firma kimliğini imzalayan değer de buna girer) kodunu verir. İki kimlik satır sonu (\n) içeremez.
Geçiş. Yalnız user_id ile hesaplanan eski user_hash çalışmaya devam eder (geriye uyumlu). Böyle bir çağrı company.id de taşıyorsa kullanıcı yine doğrulanır ama firma kimliği bağlanmaz ve saklanmaz: company.name ve company.domain yalnız eşleştirme ipucu olarak kullanılır, widget konsola sabit bir uyarı yazar ve identify olayı company_verified: false döner. Firmayı kimliğiyle bağlamak için sunucunuzda imzaya company_id'yi ekleyin: Node identityHash(secret, userId, companyId), Go IdentityCompanyHash(secret, userID, companyID); PHP ve Laravel yardımcıları aynı formülü kendi sürümlerinde alır.
// Adım 1: sunucunuzdan aldığınız user_hash ile giriş yapmış kullanıcıyı tanıtın.
// @ts-check
ClickFix("boot", { widgetId: "WIDGET_ID", locale: "tr" });
fetch("/api/support-session") // sizin ucunuz: { user_id, user_hash, company_id } döner
.then((r) => r.json())
.then((session) =>
ClickFix("identify", {
user_id: session.user_id, // kişinin dış kimliği
// Firma ile: hex(HMAC-SHA256(anahtar, user_id + "\n" + company_id)), sunucuda.
// Firmasız: hex(HMAC-SHA256(anahtar, user_id)).
user_hash: session.user_hash,
name: "Ayşe Yılmaz",
email: "ayse@example.com",
company: { id: session.company_id }, // imzadaki firma kimliği
}),
);
ClickFix("on", "error", (e) => {
if (e.code === "WIDGET_IDENTITY_INVALID")
console.warn("user_hash eşleşmedi:", e.message);
});// Adım 1: user_hash sunucuda hesaplanır ve giriş yapmış kullanıcıya verilir.
import { identityHash } from "@getclickfix/node";
// Panel > Web widget > Güvenlik > Kimlik doğrulama anahtarı (bir kez gösterilir)
const secret = process.env.CLICKFIX_IDENTITY_SECRET!;
// Oturum açmış kullanıcının sizin sisteminizdeki kimliği (dış kimlik) ve
// firmasının kimliği (identify'daki company.id). Firma verilirse imza ikisini
// birden kapsar: hex(HMAC-SHA256(anahtar, user_id + "\n" + company_id)).
export function userHashFor(userId: string, companyId?: string): string {
return identityHash(secret, userId, companyId);
}
// Örnek: /api/support-session uç noktanız bunu döner, tarayıcı identify'a verir
console.log(
JSON.stringify({
user_id: "1001",
user_hash: userHashFor("1001"), // firmasız: eski formül
company_id: "F-77",
company_user_hash: userHashFor("1001", "F-77"), // company: { id: "F-77" } ile gönderilir
}),
);package main
import (
"context"
"encoding/json"
"os"
clickfix "github.com/ClickSolveMainTR/clickfix-sdks/go"
)
// Adım 1: user_hash sunucuda hesaplanır; anahtar tarayıcıya ve uygulamaya gitmez.
func userHash(ctx context.Context) error {
secret := os.Getenv("CLICKFIX_IDENTITY_SECRET") // Panel > Web widget > Güvenlik
userID := "1001" // kullanıcının sizin sisteminizdeki kimliği
companyID := "F-77" // firmasının kimliği (identify: company.id)
return json.NewEncoder(os.Stdout).Encode(map[string]string{
"user_id": userID,
"user_hash": clickfix.IdentityHash(secret, userID), // firmasız: eski formül
// Firma ile: imza user_id + "\n" + company_id üzerinden; company: { id } ile gönderilir.
"company_id": companyID,
"company_user_hash": clickfix.IdentityCompanyHash(secret, userID, companyID),
})
}<?php
// Adım 1: user_hash sunucuda hesaplanır ve giriş yapmış kullanıcıya verilir.
require __DIR__.'/vendor/autoload.php'; // composer require clickfix/sdk
use ClickFix\UserHash;
// Panel > Web widget > Güvenlik > Kimlik doğrulama anahtarı (bir kez gösterilir)
$secret = getenv('CLICKFIX_IDENTITY_SECRET');
// Oturum açmış kullanıcının sizin sisteminizdeki kimliği (dış kimlik)
function userHashFor(string $secret, string $userId): string
{
return UserHash::make($secret, $userId);
}
// Örnek: /api/support-session uç noktanız bunu döner, tarayıcı identify'a verir
echo json_encode(['user_id' => '1001', 'user_hash' => userHashFor($secret, '1001')]), PHP_EOL;# Adım 1: user_hash = hex(HMAC-SHA256(kimlik_anahtari, user_id)); yalnız sunucuda.
USER_ID="1001"
printf '%s' "$USER_ID" | openssl dgst -sha256 -hmac "$CLICKFIX_IDENTITY_SECRET" | sed 's/^.* //'
# Firma ile (company: { id }): imza user_id + "\n" + company_id üzerinden hesaplanır.
COMPANY_ID="F-77"
printf '%s\n%s' "$USER_ID" "$COMPANY_ID" | openssl dgst -sha256 -hmac "$CLICKFIX_IDENTITY_SECRET" | sed 's/^.* //'Anahtarı Anahtarı yenile ile değiştirirseniz eski anahtarla üretilen değerler geçersiz olur; sunucunuzdaki anahtarı aynı anda güncelleyin. Çerez onayı gerektiren sitelerde boot({ consent }) ya da ClickFix("consent", true) ile onayı iletin; onay gelene kadar tarayıcıya hiçbir şey yazılmaz.
2. Dış kimlik: kişi ve şirket
Kendi sisteminizdeki numaralar ClickFix'te dış kimlik (external_id) olarak tutulur. Kişi ve şirket dış kimlikleri ayrı alanlardır, birbirinden bağımsızdır ve organizasyon içinde tekildir:
| Kayıt | API alanı | Widget identify karşılığı | Sistem bağlamada |
|---|---|---|---|
| Kişi | external_id | user_id | {external_id} |
| Şirket | external_id | company.id | {company_external_id} |
PUT /v1/public-api/contacts/by-external-id kişiyi dış kimliğiyle oluşturur ya da günceller (201 oluşturuldu, 200 güncellendi). Eşleşme yalnız dış kimlikle yapılır; e-posta ya da telefon üzerinden başka bir kişi devralınmaz. Başka kişide olan bir kimlik 409 EXTERNAL_ID_EXISTS döner. Kişiyi önce API ile açıp sonra kullanıcı identify ile gelirse yeni kişi açılmaz, aynı kişiye bağlanır; temsilcinin müşteri siteye girmeden açtığı konuşma (contact_external_id ile) kullanıcı oturum açınca widget'ta görünür.
// Adım 2: kişi ve şirket için ayrı dış kimlik. user_id kişinin, company.id şirketin.
// @ts-check
ClickFix("identify", {
user_id: "1001", // API: contacts.external_id
user_hash: "SUNUCUDA_HESAPLANAN", // user_id + "\n" + company.id üzerinden imzalı
company: { id: "F-77", name: "Acme A.Ş.", domain: "acme.com.tr" }, // API: companies.external_id
});
ClickFix("on", "identify", ({ verified, company_verified }) =>
console.log("doğrulandı:", verified, "firma:", company_verified),
);// Adım 2: kişiyi dış kimliğiyle oluştur/güncelle, şirketi dış kimliğiyle bul.
import { createClient } from "@getclickfix/node";
const client = createClient({
baseURL: process.env.CLICKFIX_BASE_URL!, // https://api.getclickfix.com
apiKey: process.env.CLICKFIX_API_KEY!, // contacts:read, contacts:write, companies:read
});
// Aynı dış kimlikle ikinci çağrı yeni kişi açmaz, günceller (200); ilki 201 döner.
const contact = await client.request(
"/v1/public-api/contacts/by-external-id",
"put",
{ external_id: "1001", name: "Ayşe Yılmaz", email: "ayse@example.com" },
{ idempotencyKey: `user-1001-${Date.now()}` },
);
// Şirketin dış kimliği kişininkinden ayrıdır (widget: company.id).
const companies = await client.request(
"/v1/public-api/companies",
"get",
undefined,
{ query: { external_id: "F-77" } },
);
console.log(
JSON.stringify({ contact: contact.data, companies: companies.data }),
);package main
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
clickfix "github.com/ClickSolveMainTR/clickfix-sdks/go"
)
// Adım 2: kişiyi dış kimliğiyle oluştur/güncelle, şirketi dış kimliğiyle bul.
func externalID(ctx context.Context) error {
client, err := clickfix.New(os.Getenv("CLICKFIX_BASE_URL"), os.Getenv("CLICKFIX_API_KEY"))
if err != nil {
return err
}
name, email := "Ayşe Yılmaz", "ayse@example.com"
key := fmt.Sprintf("user-1001-%d", time.Now().UnixNano())
res, err := client.PutV1PublicApiContactByExternalIdWithResponse(ctx,
&clickfix.PutV1PublicApiContactByExternalIdParams{IdempotencyKey: key},
clickfix.PutV1PublicApiContactByExternalIdJSONRequestBody{ExternalId: "1001", Name: &name, Email: &email})
if err != nil {
return err
}
if res.StatusCode() != 200 && res.StatusCode() != 201 { // 200 güncellendi, 201 oluşturuldu
return fmt.Errorf("clickfix: HTTP %d", res.StatusCode())
}
ext := "F-77" // şirketin dış kimliği (widget identify: company.id)
companies, err := client.GetV1PublicApiCompaniesWithResponse(ctx, &clickfix.GetV1PublicApiCompaniesParams{ExternalId: &ext})
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(map[string]any{"contact": res.StatusCode(), "companies": companies.StatusCode()})
}<?php
// Adım 2: kişiyi dış kimliğiyle oluştur/güncelle, şirketi dış kimliğiyle bul.
require __DIR__.'/vendor/autoload.php'; // composer require clickfix/sdk guzzlehttp/guzzle
use ClickFix\Client;
$client = new Client(
getenv('CLICKFIX_BASE_URL'), // https://api.getclickfix.com
getenv('CLICKFIX_API_KEY'), // contacts:read, contacts:write, companies:read
);
// Aynı dış kimlikle ikinci çağrı yeni kişi açmaz, günceller (200); ilki 201 döner.
$contact = $client->contacts()->upsertByExternalId(
'1001',
['name' => 'Ayşe Yılmaz', 'email' => 'ayse@example.com'],
idempotencyKey: 'user-1001-'.(int) (microtime(true) * 1000),
);
// Şirketin dış kimliği kişininkinden ayrıdır (widget: company.id).
$company = $client->companies()->findByExternalId('F-77');
echo json_encode(['contact' => $contact, 'company' => $company]), PHP_EOL;# Adım 2: kişiyi dış kimliğiyle oluştur/güncelle (201 oluşturuldu, 200 güncellendi).
curl -sS --fail-with-body -X PUT "$CLICKFIX_BASE_URL/v1/public-api/contacts/by-external-id" \
-H "Authorization: Bearer $CLICKFIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: user-1001-$(date +%s)" \
-d '{ "external_id": "1001", "name": "Ayşe Yılmaz", "email": "ayse@example.com" }'
# Şirketi kendi dış kimliğiyle bul (kişi dış kimliğinden ayrıdır).
curl -sS --fail-with-body "$CLICKFIX_BASE_URL/v1/public-api/companies?external_id=F-77" \
-H "Authorization: Bearer $CLICKFIX_API_KEY"Konuşma ve kişi yanıtlarındaki identity_state alanı verified, anonymous ya da self_named değerini alır; webhook olayları contact_external_id, contact_verified, company_uuid ve company_external_id taşır, böylece kendi kaydınızı ek istek atmadan eşleştirirsiniz.
3. Özel alanlar
Özel alanı Ayarlar → Özel alanlar altında bir kez tanımlarsınız; tanımlamak tek adımdır. Alanın anahtarı neyse, hem API'de custom_fields içinde hem widget'ta setAttributes / identify({ attributes }) içinde o anahtarı gönderirsiniz. Değer alanın tipine göre doğrulanır; liste alanlarında seçeneğin kimliği ya da etiketi kabul edilir ve kimlik saklanır. Alanın widget erişimi anonim ziyaretçinin yazıp yazamayacağını belirler (varsayılan: yalnız doğrulanmış kullanıcı). Tanımsız bir anahtar yok sayılmaz: panelde tanımsız alan uyarısı olarak listelenir ve alanı tanımladığınızda kişiye işlenir. Kişi henüz yoksa widget sonucu pending: true döner ve değerler ilk mesajla gider.
// Adım 3: özel alanlar. Anahtar panelde tanımlı alanın anahtarıdır; tanımsız
// anahtar "tanımsız alan" uyarısı olarak kalır ve alanı tanımlayınca işlenir.
// @ts-check
ClickFix("setAttributes", {
plan: "pro",
seats: 12,
renewal_date: "2027-01-31",
});
ClickFix("on", "attributes", ({ applied, ignored, pending }) => {
console.log({ applied, ignored, pending }); // pending: ilk mesajla gönderilecek
});// Adım 3: özel alanlar. Alanı panelde tanımlayın (Ayarlar > Özel alanlar);
// anahtarı neyse custom_fields içinde o anahtarla gönderin.
import { createClient } from "@getclickfix/node";
const client = createClient({
baseURL: process.env.CLICKFIX_BASE_URL!,
apiKey: process.env.CLICKFIX_API_KEY!,
});
const contact = await client.request(
"/v1/public-api/contacts/by-external-id",
"put",
{
external_id: "1001",
// Liste alanlarında seçeneğin kimliği ya da etiketi kabul edilir.
custom_fields: { plan: "pro", seats: 12, renewal_date: "2027-01-31" },
},
{ idempotencyKey: `user-1001-fields-${Date.now()}` },
);
console.log(JSON.stringify(contact.data));package main
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
clickfix "github.com/ClickSolveMainTR/clickfix-sdks/go"
)
// Adım 3: özel alanlar; anahtar panelde tanımladığınız alanın anahtarıdır.
func customFields(ctx context.Context) error {
client, err := clickfix.New(os.Getenv("CLICKFIX_BASE_URL"), os.Getenv("CLICKFIX_API_KEY"))
if err != nil {
return err
}
fields := map[string]any{"plan": "pro", "seats": 12, "renewal_date": "2027-01-31"}
res, err := client.PutV1PublicApiContactByExternalIdWithResponse(ctx,
&clickfix.PutV1PublicApiContactByExternalIdParams{IdempotencyKey: fmt.Sprintf("user-1001-fields-%d", time.Now().UnixNano())},
clickfix.PutV1PublicApiContactByExternalIdJSONRequestBody{ExternalId: "1001", CustomFields: &fields})
if err != nil {
return err
}
if res.StatusCode() != 200 && res.StatusCode() != 201 {
return fmt.Errorf("clickfix: HTTP %d", res.StatusCode())
}
return json.NewEncoder(os.Stdout).Encode(map[string]int{"status": res.StatusCode()})
}<?php
// Adım 3: özel alanlar. Alanı panelde tanımlayın (Ayarlar > Özel alanlar);
// anahtarı neyse custom_fields içinde o anahtarla gönderin.
require __DIR__.'/vendor/autoload.php';
use ClickFix\Client;
$client = new Client(getenv('CLICKFIX_BASE_URL'), getenv('CLICKFIX_API_KEY'));
// Liste alanlarında seçeneğin kimliği ya da etiketi kabul edilir.
$contact = $client->customFields()->setForContact(
'1001',
['plan' => 'pro', 'seats' => 12, 'renewal_date' => '2027-01-31'],
idempotencyKey: 'user-1001-fields-'.(int) (microtime(true) * 1000),
);
echo json_encode($contact), PHP_EOL;# Adım 3: özel alanlar; anahtar panelde tanımlı alanın anahtarıdır.
curl -sS --fail-with-body -X PUT "$CLICKFIX_BASE_URL/v1/public-api/contacts/by-external-id" \
-H "Authorization: Bearer $CLICKFIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: user-1001-fields-$(date +%s)" \
-d '{ "external_id": "1001", "custom_fields": { "plan": "pro", "seats": 12, "renewal_date": "2027-01-31" } }'4. Şirket
Şirket, widget identify çağrısındaki company ile oluşur ya da eşleşir: birinci anahtar company.id (şirketin dış kimliği; yalnız user_hash onu da imzalıyorsa kullanılır, bkz. kimlik doğrulama), ikinci anahtar domain. İmzalı kimlikle bağlanan şirket panelde Doğrulanmış rozeti taşır. API'de şirket GET /v1/public-api/companies?external_id= ile bulunur; şirket kaydı üzerinde müşteri sağlığı için üç sinyal gönderilir: kullanım (PUT /company-usage, 0–100), gelir (PUT /company-revenue) ve ürün olayları (POST /company-events, bir sonraki bölüm). Şirket her üçünde company_uuid, external_id ya da domain ile seçilir.
// Adım 4: şirket identify ile oluşur ya da dış kimliğiyle eşleşir (ikinci anahtar: domain).
// company.id yalnız user_hash onu da imzalıyorsa bağlanır (user_id + "\n" + company.id).
// @ts-check
ClickFix("boot", {
widgetId: "WIDGET_ID",
user: {
user_id: "1001",
user_hash: "SUNUCUDA_HESAPLANAN", // user_id + "\n" + "F-77" üzerinden
company: { id: "F-77", name: "Acme A.Ş.", domain: "acme.com.tr" },
},
});// Adım 4: şirket. Şirket widget identify (company.id) ile oluşur; API ile
// bulunur ve müşteri sağlığı sinyalleri (kullanım, gelir, olay) gönderilir.
import { createClient } from "@getclickfix/node";
const client = createClient({
baseURL: process.env.CLICKFIX_BASE_URL!,
apiKey: process.env.CLICKFIX_API_KEY!, // companies:read, health:write
});
const found = await client.request(
"/v1/public-api/companies",
"get",
undefined,
{
query: { external_id: "F-77" },
},
);
// 0-100 arası kullanım sinyali; 30 gün boyunca sağlık puanına katılır.
const usage = await client.request(
"/v1/public-api/company-usage",
"put",
{ external_id: "F-77", score: 82 },
// İşlemi tanımlayan sabit değer: kendi ölçüm kaydınızın kimliği (örnekte zaman damgası)
{ idempotencyKey: `usage-F-77-${Date.now()}` },
);
console.log(JSON.stringify({ found: found.data, usage: usage.data }));package main
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
clickfix "github.com/ClickSolveMainTR/clickfix-sdks/go"
)
// Adım 4: şirketi bul ve kullanım sinyali gönder (health:write).
func company(ctx context.Context) error {
client, err := clickfix.New(os.Getenv("CLICKFIX_BASE_URL"), os.Getenv("CLICKFIX_API_KEY"))
if err != nil {
return err
}
ext := "F-77"
found, err := client.GetV1PublicApiCompaniesWithResponse(ctx, &clickfix.GetV1PublicApiCompaniesParams{ExternalId: &ext})
if err != nil {
return err
}
usage, err := client.PutV1PublicApiCompanyUsageWithResponse(ctx,
// İşlemi tanımlayan sabit değer: kendi ölçüm kaydınızın kimliği (örnekte zaman damgası)
&clickfix.PutV1PublicApiCompanyUsageParams{IdempotencyKey: fmt.Sprintf("usage-F-77-%d", time.Now().UnixNano())},
clickfix.PutV1PublicApiCompanyUsageJSONRequestBody{ExternalId: &ext, Score: 82})
if err != nil {
return err
}
if usage.StatusCode() != 200 {
return fmt.Errorf("clickfix: HTTP %d", usage.StatusCode())
}
return json.NewEncoder(os.Stdout).Encode(map[string]int{"found": found.StatusCode(), "usage": usage.StatusCode()})
}<?php
// Adım 4: şirket. Şirket widget identify (company.id) ile oluşur; API ile
// bulunur ve müşteri sağlığı sinyalleri (kullanım, gelir, olay) gönderilir.
require __DIR__.'/vendor/autoload.php';
use ClickFix\Client;
$client = new Client(
getenv('CLICKFIX_BASE_URL'),
getenv('CLICKFIX_API_KEY'), // companies:read, health:write
);
$found = $client->companies()->findByExternalId('F-77');
// 0-100 arası kullanım sinyali; 30 gün boyunca sağlık puanına katılır.
$usage = $client->companies()->setUsage(
['external_id' => 'F-77', 'score' => 82],
// İşlemi tanımlayan sabit değer: kendi ölçüm kaydınızın kimliği (örnekte zaman damgası)
idempotencyKey: 'usage-F-77-'.(int) (microtime(true) * 1000),
);
echo json_encode(['found' => $found, 'usage' => $usage]), PHP_EOL;# Adım 4: şirket kullanım sinyali (0-100, health:write); şirket external_id ile bulunur.
curl -sS --fail-with-body -X PUT "$CLICKFIX_BASE_URL/v1/public-api/company-usage" \
-H "Authorization: Bearer $CLICKFIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: usage-F-77-$(date +%s%N)" \
-d '{ "external_id": "F-77", "score": 82 }'5. Olaylar ve ekran bağlamı
İki ayrı olay kanalı vardır:
- Ekran bağlamı (widget):
ClickFix("track", ad, özellikler)ile kaydettiğiniz son 20 olay ve sayfa adresi, ziyaretçinin sonraki mesajıyla gider. Panelde konuşma başlığında hangi ekrandan yazdı olarak görünür, AI ajanının girdisine ve iş akışı koşullarına katılır. Sorgu dizesi ve#parçası sunucuda atılır; kişisel veri taşıyan adresleri yine detracközelliklerine koymayın. - Ürün olayları (API,
events:write):POST /v1/public-api/company-eventsşirket zaman çizelgesine yazar;contact_emailolayı bilinen kişiye bağlar,idempotency_keyaynı olayın iki kez yazılmasını önler (duplicate: true).
// Adım 5: ekran bağlamı. Son 20 olay ve sayfa adresi ziyaretçinin mesajıyla
// gider; panelde "hangi ekrandan yazdı" ve AI'ın girdisi olur.
// @ts-check
ClickFix("track", "order_viewed", { order_id: "SP-1042", total: 1250.5 });
ClickFix("track", "payment_failed", { reason: "insufficient_funds" });
// Kullanıcı destek isterse konuşmayı hazır metinle açın:
ClickFix(
"startConversation",
"SP-1042 numaralı siparişimde ödeme hatası alıyorum",
);// Adım 5: olaylar. Ekran bağlamı widget'tan (track) gelir; ürün olayları
// şirket zaman çizelgesine API ile yazılır (events:write).
import { createClient } from "@getclickfix/node";
const client = createClient({
baseURL: process.env.CLICKFIX_BASE_URL!,
apiKey: process.env.CLICKFIX_API_KEY!,
});
// Olayı sizin sisteminizde tanımlayan kimlik; tekrar gönderimde aynı kalır (örnekte zaman damgası)
const eventId = `seat-limit-F-77-${Date.now()}`;
const event = await client.request(
"/v1/public-api/company-events",
"post",
{
external_id: "F-77", // şirketin dış kimliği (ya da company_uuid / domain)
contact_email: "ayse@example.com", // olayı bilinen kişiye bağlar
name: "seat_limit_reached",
properties: { seats: 12, limit: 12 },
idempotency_key: eventId,
},
{ idempotencyKey: eventId },
);
console.log(JSON.stringify(event.data));package main
import (
"context"
"encoding/json"
"fmt"
"os"
"time"
clickfix "github.com/ClickSolveMainTR/clickfix-sdks/go"
)
// Adım 5: ürün olayını şirket zaman çizelgesine yaz (events:write).
func events(ctx context.Context) error {
client, err := clickfix.New(os.Getenv("CLICKFIX_BASE_URL"), os.Getenv("CLICKFIX_API_KEY"))
if err != nil {
return err
}
// Olayı sizin sisteminizde tanımlayan kimlik; tekrar gönderimde aynı kalır (örnekte zaman damgası)
ext, email, idem := "F-77", "ayse@example.com", fmt.Sprintf("seat-limit-F-77-%d", time.Now().UnixNano())
props := map[string]any{"seats": 12, "limit": 12}
res, err := client.PostV1PublicApiCompanyEventWithResponse(ctx,
&clickfix.PostV1PublicApiCompanyEventParams{IdempotencyKey: idem},
clickfix.PostV1PublicApiCompanyEventJSONRequestBody{
ExternalId: &ext, ContactEmail: &email, Name: "seat_limit_reached", Properties: &props, IdempotencyKey: &idem,
})
if err != nil {
return err
}
if res.StatusCode() != 200 && res.StatusCode() != 201 {
return fmt.Errorf("clickfix: HTTP %d", res.StatusCode())
}
return json.NewEncoder(os.Stdout).Encode(map[string]int{"status": res.StatusCode()})
}<?php
// Adım 5: olaylar. Ekran bağlamı widget'tan (track) gelir; ürün olayları
// şirket zaman çizelgesine API ile yazılır (events:write).
require __DIR__.'/vendor/autoload.php';
use ClickFix\Client;
$client = new Client(getenv('CLICKFIX_BASE_URL'), getenv('CLICKFIX_API_KEY'));
// Olayı sizin sisteminizde tanımlayan kimlik; tekrar gönderimde aynı kalır (örnekte zaman damgası)
$eventId = 'seat-limit-F-77-'.(int) (microtime(true) * 1000);
// idempotency_key aynı zamanda Idempotency-Key başlığı olur.
$event = $client->events()->track([
'external_id' => 'F-77', // şirketin dış kimliği (ya da company_uuid / domain)
'contact_email' => 'ayse@example.com', // olayı bilinen kişiye bağlar
'name' => 'seat_limit_reached',
'properties' => ['seats' => 12, 'limit' => 12],
'idempotency_key' => $eventId,
]);
echo json_encode($event), PHP_EOL;# Adım 5: ürün olayını şirket zaman çizelgesine yaz (events:write).
# Olayı sizin sisteminizde tanımlayan kimlik; tekrar gönderimde aynı kalır (örnekte zaman damgası)
EVENT_ID="seat-limit-F-77-$(date +%s%N)"
curl -sS --fail-with-body -X POST "$CLICKFIX_BASE_URL/v1/public-api/company-events" \
-H "Authorization: Bearer $CLICKFIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $EVENT_ID" \
-d '{ "external_id": "F-77", "contact_email": "ayse@example.com", "name": "seat_limit_reached", "properties": { "seats": 12, "limit": 12 }, "idempotency_key": "'$EVENT_ID'" }'6. Sistem bağlama
Entegrasyonlar → Sistemini bağla ekranı ClickFix'in sizin sisteminizi çağırmasını sağlar; ters yönde bir API değildir. Siz yalnız müşterinin dış kimliğiyle cevap veren bir uç yazarsınız, gerisi panelde kurulur:
- Bağlantı: sisteminizin temel adresi (yalnız herkese açık
https) ve kimlik doğrulaması (yok, Bearer, özel başlık ya da kullanıcı adı/parola). Gizli değer şifreli saklanır, bir daha gösterilmez, loglarda maskelenir. - Kavramlar: sisteminizdeki nesneler (Fatura, Sipariş, Lisans…). Her kavram bir özel nesne tipidir.
- Kaynak: kavramın hangi uçtan okunacağı, temel adrese göre:
/customers/{external_id}/invoices.{external_id}müşterinin,{company_external_id}firmasının sizin sisteminizdeki kimliğidir; ikisinden biri uçta bulunmalıdır. Dene ile gerçek bir kimlikle çağırır, kayıtların cevabın neresinde olduğunu (items) ve AI'ın göreceği alanları cevaptan tıklayarak seçersiniz. - İşler: "Faturayı tekrar gönder" gibi eylemler: yöntem, uç, parametreler, onay gerekip gerekmediği ve kimin kullanabileceği (AI, temsilci, iş akışı).
- AI tarifleri: bir niyet için hangi kavramların okunacağı ve hangi işlerin yapılabileceği.
ClickFix çağrıyı yalnız herkese açık adreslere yapar (iç ağ, yerel ve bulut meta veri adresleri reddedilir), yönlendirme izlemez, cevabı 256 KB ile sınırlar ve organizasyon başına dakikada 60 çağrı yapar. Ucunuzun şekli serbesttir; aşağıdaki örnek, panelde /customers/{external_id}/invoices ve kayıt yolu items olarak kurulan bir kaynağı karşılar. curl örneği panelin Dene düğmesinin attığı çağrının aynısıdır.
// Adım 8: sistem bağlama için widget'ta ek bir şey yoktur; ClickFix sisteminizi
// identify'daki user_id (dış kimlik) ile çağırır. Doğru kimliği verdiğinizden emin olun:
// @ts-check
ClickFix("on", "ready", () => {
const state = ClickFix("getState");
if (state && !state.verified)
console.warn("identify yapılmadı: sistem verisi eşleşmez");
});// Adım 8: sistem bağlama. ClickFix sizin sisteminizi çağırır; siz yalnız
// müşterinin dış kimliğiyle cevap veren bir uç yazarsınız. Panelde
// Entegrasyonlar > Sistemini bağla > Kaynak: /customers/{external_id}/invoices
import { createServer } from "node:http";
const token = process.env.SYSTEM_TOKEN ?? "example-system-token"; // bağlantıda Bearer
createServer((req, res) => {
if (req.headers.authorization !== `Bearer ${token}`) {
res.writeHead(401).end();
return;
}
const m = /^\/customers\/([^/]+)\/invoices$/.exec(req.url ?? "");
if (!m) {
res.writeHead(404).end();
return;
}
const externalId = decodeURIComponent(m[1]);
// Gerçek sisteminizde: veritabanından externalId'nin faturaları
const items = [
{
number: "F-2026-0042",
total: 1250.5,
currency: "TRY",
status: "paid",
customer_id: externalId,
},
];
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ items })); // Kayıt yolu: items
}).listen(Number(process.env.PORT ?? 3000), () => console.log("listening"));package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
// Adım 8: sistem bağlama. ClickFix bu ucu müşterinin dış kimliğiyle çağırır:
// Panel > Entegrasyonlar > Sistemini bağla > Kaynak: /customers/{external_id}/invoices
func systemEndpoint(ctx context.Context) error {
token := os.Getenv("SYSTEM_TOKEN") // bağlantıda Bearer olarak girilir
if token == "" {
token = "example-system-token"
}
http.HandleFunc("GET /customers/{external_id}/invoices", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer "+token {
w.WriteHeader(http.StatusUnauthorized)
return
}
externalID := r.PathValue("external_id")
// Gerçek sisteminizde: veritabanından externalID'nin faturaları
items := []map[string]any{{"number": "F-2026-0042", "total": 1250.5, "currency": "TRY", "status": "paid", "customer_id": externalID}}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"items": items}) // Kayıt yolu: items
})
port := strings.TrimSpace(os.Getenv("PORT"))
if port == "" {
port = "3000"
}
fmt.Println("listening")
return http.ListenAndServe("127.0.0.1:"+port, nil)
}<?php
// Adım 8: sistem bağlama. ClickFix sizin sisteminizi çağırır; siz yalnız
// müşterinin dış kimliğiyle cevap veren bir uç yazarsınız. Panelde
// Entegrasyonlar > Sistemini bağla > Kaynak: /customers/{external_id}/invoices
// Çalıştırma: php -S 127.0.0.1:3000 08-system-endpoint.php (ya da framework rotanız)
$token = getenv('SYSTEM_TOKEN') ?: 'example-system-token'; // bağlantıda Bearer
if (($_SERVER['HTTP_AUTHORIZATION'] ?? '') !== 'Bearer '.$token) {
http_response_code(401);
exit;
}
$path = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?: '';
if (!preg_match('#^/customers/([^/]+)/invoices$#', $path, $m)) {
http_response_code(404);
exit;
}
$externalId = rawurldecode($m[1]);
// Gerçek sisteminizde: veritabanından externalId'nin faturaları
$items = [
[
'number' => 'F-2026-0042',
'total' => 1250.5,
'currency' => 'TRY',
'status' => 'paid',
'customer_id' => $externalId,
],
];
header('Content-Type: application/json');
echo json_encode(['items' => $items]); // Kayıt yolu: items# Adım 8: ClickFix'in sisteminize atacağı çağrının aynısı (Sistemini bağla > Dene düğmesi).
# SYSTEM_BASE_URL sizin sisteminiz, {external_id} müşterinin dış kimliğidir.
curl -sS --fail-with-body "$SYSTEM_BASE_URL/customers/1001/invoices" \
-H "Authorization: Bearer $SYSTEM_TOKEN"Kavram verisi müşterinin dış kimliğiyle okunur; bu yüzden sistem bağlama ancak dış kimlik kurulduysa çalışır. Panelin Bu müşteri olarak dene önizlemesi bir dış kimlik için neyin kabul edildiğini, hangi kavramların okunduğunu ve AI'ın göreceği bağlamı gösterir.
7. API kanalı
Kendi sohbet uygulamanızı ya da sisteminizi bir kanal olarak bağlamak için API kanalı kullanılır; web sitesinde buna gerek yoktur, widget zaten kanaldır. Kanal Kanallar → Kanal ekle → API ile kurulur; size gelen adres (/v1/webhooks/channels/api/{instance}), bir imza anahtarı ve temsilci yanıtlarının gönderileceği adresi girmeniz için bir alan verilir.
- Müşteri mesajı → ClickFix:
POST /v1/webhooks/channels/api/{instance}gövdesi{ id, sender_id, sender_name?, text, thread_id?, reply_to?, at? }. İstek API anahtarıyla değil kanalın imza anahtarıyla doğrulanır:X-ClickFix-Timestamp(Unix saniye) veX-ClickFix-Signature: sha256=+ hex(HMAC-SHA256(imza_anahtarı, timestamp + "." + ham_gövde)).sender_idmüşterinin sizin sisteminizdeki kimliğidir; aynıidbir kez kaydedilir. - Temsilci yanıtı → siz: ClickFix, kanalda tanımladığınız adrese
apiChannelReplygövdesiylePOSTatar; aynı imza başlıkları ve mesaj UUID'sini taşıyanIdempotency-Keyile. Mesajı kaydettikten sonra2xxdönün;408ve429dışındaki4xxkalıcı hata sayılır. Doğrulama webhook imzasıyla aynıdır (v1=yerinesha256=).
// Adım 6: web'de API kanalı gerekmez; widget zaten kanaldır. Kendi arayüzünüz
// varsa @getclickfix/react-widget'ın arayüzsüz istemcisini kullanın.
// @ts-check
ClickFix("on", "message", (m) => {
// Temsilci veya AI yanıtı: kendi bildirim sisteminize aktarabilirsiniz
console.log(m.conversation_uuid, m.sender_type, m.text);
});// Adım 6: API kanalı. Kendi uygulamanızdaki müşteri mesajını ClickFix'e
// gönderin; imza API anahtarıyla değil kanalın imza anahtarıyla atılır.
import { createHmac } from "node:crypto";
const base = process.env.CLICKFIX_BASE_URL!;
const instance = process.env.CLICKFIX_CHANNEL_INSTANCE!; // Kanallar > API kanalı
const secret = process.env.CLICKFIX_CHANNEL_SECRET!;
const body = JSON.stringify({
id: `msg-${Date.now()}`, // kanal içinde tekil; aynı id bir kez kaydedilir
sender_id: "1001", // müşterinin sizin sisteminizdeki kimliği
sender_name: "Ayşe Yılmaz",
text: "Siparişim ne zaman gelir?",
});
const timestamp = String(Math.floor(Date.now() / 1000));
const signature =
"sha256=" +
createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(body)
.digest("hex");
const res = await fetch(`${base}/v1/webhooks/channels/api/${instance}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-ClickFix-Timestamp": timestamp,
"X-ClickFix-Signature": signature,
},
body,
});
if (!res.ok) throw new Error(`ClickFix ${res.status}`);
console.log(JSON.stringify(await res.json()));package main
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"time"
)
// Adım 6: API kanalına müşteri mesajı gönder; imza kanalın imza anahtarıyla.
func apiChannel(ctx context.Context) error {
base, instance, secret := os.Getenv("CLICKFIX_BASE_URL"), os.Getenv("CLICKFIX_CHANNEL_INSTANCE"), os.Getenv("CLICKFIX_CHANNEL_SECRET")
body, _ := json.Marshal(map[string]string{
"id": fmt.Sprintf("msg-%d", time.Now().UnixNano()), // kanal içinde tekil
"sender_id": "1001", // müşterinin sizin sisteminizdeki kimliği
"sender_name": "Ayşe Yılmaz",
"text": "Siparişim ne zaman gelir?",
})
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(timestamp + "."))
mac.Write(body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, base+"/v1/webhooks/channels/api/"+instance, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-ClickFix-Timestamp", timestamp)
req.Header.Set("X-ClickFix-Signature", "sha256="+hex.EncodeToString(mac.Sum(nil)))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("clickfix: HTTP %d", res.StatusCode)
}
return json.NewEncoder(os.Stdout).Encode(map[string]int{"status": res.StatusCode})
}<?php
// Adım 6: API kanalı. Kendi uygulamanızdaki müşteri mesajını ClickFix'e
// gönderin; imza API anahtarıyla değil kanalın imza anahtarıyla atılır.
require __DIR__.'/vendor/autoload.php';
use ClickFix\ApiChannel;
$channel = new ApiChannel(
getenv('CLICKFIX_BASE_URL'),
getenv('CLICKFIX_CHANNEL_INSTANCE'), // Kanallar > API kanalı
getenv('CLICKFIX_CHANNEL_SECRET'),
);
// X-ClickFix-Timestamp ve X-ClickFix-Signature başlıklarını SDK ekler.
$received = $channel->send([
'id' => 'msg-'.(int) (microtime(true) * 1000), // kanal içinde tekil; aynı id bir kez kaydedilir
'sender_id' => '1001', // müşterinin sizin sisteminizdeki kimliği
'sender_name' => 'Ayşe Yılmaz',
'text' => 'Siparişim ne zaman gelir?',
]);
echo json_encode($received), PHP_EOL;# Adım 6: API kanalına müşteri mesajı; imza kanalın imza anahtarıyla (API anahtarı değil).
BODY='{"id":"msg-'$(date +%s)'","sender_id":"1001","sender_name":"Ayşe Yılmaz","text":"Siparişim ne zaman gelir?"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$CLICKFIX_CHANNEL_SECRET" | sed 's/^.* //')
curl -sS --fail-with-body -X POST "$CLICKFIX_BASE_URL/v1/webhooks/channels/api/$CLICKFIX_CHANNEL_INSTANCE" \
-H "Content-Type: application/json" \
-H "X-ClickFix-Timestamp: $TS" \
-H "X-ClickFix-Signature: sha256=$SIG" \
-d "$BODY"8. Webhook'lar
Webhook'lar olayları (yeni konuşma, gelen mesaj, kişi güncellemesi, SLA ihlali, memnuniyet…) imzalı POST ile size iletir; yoklama gerekmez. Abonelik webhooks:write kapsamıyla POST /v1/public-api/webhooks ile açılır; yanıttaki data.secret yalnız o yanıtta döner. Adres herkese açık https olmalıdır. Her teslimat ClickFix-Webhook-Id, ClickFix-Webhook-Event, ClickFix-Webhook-Timestamp ve ClickFix-Webhook-Signature: v1=… başlıklarını taşır. Doğrulama: ham gövde üzerinde hex(HMAC-SHA256(secret, timestamp + "." + ham_gövde)), sabit zamanlı karşılaştırma, 5 dakika tolerans, ClickFix-Webhook-Id ile tekilleştirme; sonra 2xx. Başarısız teslimatlar artan aralıklarla 24 saate kadar tekrarlanır. Olay listesi ve gövde biçimi: Webhook'lar.
// Adım 7: tarayıcı tarafında webhook yoktur; konuşma olaylarını SDK verir.
// @ts-check
ClickFix("on", "conversation:started", ({ conversation_uuid }) =>
console.log("yeni", conversation_uuid),
);
ClickFix("on", "conversation:ended", ({ conversation_uuid, status }) =>
console.log(conversation_uuid, status),
);
ClickFix("on", "csat", ({ score }) => console.log("memnuniyet", score));// Adım 7: webhook aboneliği aç ve teslimatları ham gövde üzerinde doğrula.
import { createServer } from "node:http";
import { createClient, verifyWebhook } from "@getclickfix/node";
const client = createClient({
baseURL: process.env.CLICKFIX_BASE_URL!,
apiKey: process.env.CLICKFIX_API_KEY!, // webhooks:write
});
const created = await client.request(
"/v1/public-api/webhooks",
"post",
{
name: "CRM senkronizasyonu",
url: "https://example.com/clickfix/webhook",
events: ["conversation.created", "message.received", "contact.updated"],
enabled: true,
},
{ idempotencyKey: `webhook-crm-${Date.now()}` },
);
// data.secret yalnız bu yanıtta döner: güvenli saklayın (ör. gizli değer deposu),
// loglamayın. Doğrulama sunucunuz onu oradan okur.
const { secret: issued, ...subscription } = created.data as {
secret: string;
uuid: string;
};
const secret = process.env.CLICKFIX_WEBHOOK_SECRET ?? issued;
console.log(JSON.stringify({ subscription }));
createServer(async (req, res) => {
const chunks: Buffer[] = [];
for await (const c of req) chunks.push(c as Buffer);
const raw = Buffer.concat(chunks);
const ok = verifyWebhook(
secret,
String(req.headers["clickfix-webhook-timestamp"] ?? ""),
String(req.headers["clickfix-webhook-signature"] ?? ""),
raw,
);
if (!ok) {
res.writeHead(401).end();
return;
}
// ClickFix-Webhook-Id ile tekilleştirin, kaydedin, sonra 2xx dönün.
const event = JSON.parse(raw.toString()) as { event: string; data: unknown };
console.log(JSON.stringify({ received: event.event }));
res.writeHead(200).end();
}).listen(Number(process.env.PORT ?? 3000), () => console.log("listening"));package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
clickfix "github.com/ClickSolveMainTR/clickfix-sdks/go"
)
// Adım 7: webhook aboneliği aç ve teslimatları ham gövde üzerinde doğrula.
func webhooks(ctx context.Context) error {
client, err := clickfix.New(os.Getenv("CLICKFIX_BASE_URL"), os.Getenv("CLICKFIX_API_KEY"))
if err != nil {
return err
}
res, err := client.PostV1PublicApiWebhooksWithResponse(ctx,
&clickfix.PostV1PublicApiWebhooksParams{IdempotencyKey: fmt.Sprintf("webhook-crm-%d", time.Now().UnixNano())},
clickfix.PostV1PublicApiWebhooksJSONRequestBody{
Name: "CRM senkronizasyonu", Url: "https://example.com/clickfix/webhook",
Events: []string{"conversation.created", "message.received", "contact.updated"}, Enabled: true,
})
if err != nil {
return err
}
if res.StatusCode() != 201 {
return fmt.Errorf("clickfix: HTTP %d", res.StatusCode())
}
var created struct {
Data struct {
Secret string `json:"secret"` // yalnız bu yanıtta döner
} `json:"data"`
}
if err := json.Unmarshal(res.Body, &created); err != nil {
return err
}
// Yalnız bu yanıtta döner: güvenli saklayın, loglamayın; sunucu onu oradan okur.
secret := os.Getenv("CLICKFIX_WEBHOOK_SECRET")
if secret == "" {
secret = created.Data.Secret
}
fmt.Println(`{"subscription":"created"}`)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
raw, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if err := clickfix.VerifyWebhook(secret, r.Header.Get("ClickFix-Webhook-Timestamp"),
r.Header.Get("ClickFix-Webhook-Signature"), raw, time.Now(), 5*time.Minute); err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
// ClickFix-Webhook-Id ile tekilleştirin, kaydedin, sonra 2xx dönün.
var event struct {
Event string `json:"event"`
}
_ = json.Unmarshal(raw, &event)
fmt.Printf(`{"received":%q}`+"\n", event.Event)
w.WriteHeader(http.StatusOK)
})
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
fmt.Println("listening")
return http.ListenAndServe("127.0.0.1:"+port, nil)
}<?php
// Adım 7: webhook aboneliği aç ve teslimatları ham gövde üzerinde doğrula.
// Komut satırından çalışınca aboneliği açar; web sunucusu altında
// (php -S 127.0.0.1:3000 07-webhooks.php ya da framework rotanız) teslimatları doğrular.
require __DIR__.'/vendor/autoload.php';
use ClickFix\Client;
use ClickFix\Webhooks;
if (PHP_SAPI === 'cli') {
$client = new Client(
getenv('CLICKFIX_BASE_URL'),
getenv('CLICKFIX_API_KEY'), // webhooks:write
);
$created = $client->webhooks()->create([
'name' => 'CRM senkronizasyonu',
'url' => 'https://example.com/clickfix/webhook',
'events' => ['conversation.created', 'message.received', 'contact.updated'],
'enabled' => true,
], idempotencyKey: 'webhook-crm-'.(int) (microtime(true) * 1000));
// secret yalnız bu yanıtta döner: güvenli saklayın (ör. gizli değer deposu),
// loglamayın. Doğrulama sunucunuz onu oradan okur (örnekte CLICKFIX_WEBHOOK_SECRET).
unset($created['secret']);
echo json_encode(['subscription' => $created]), PHP_EOL;
exit;
}
$secret = getenv('CLICKFIX_WEBHOOK_SECRET');
$raw = file_get_contents('php://input'); // ham gövde; JSON'a çevirmeden doğrulayın
$ok = Webhooks::verify(
$secret,
$_SERVER['HTTP_CLICKFIX_WEBHOOK_TIMESTAMP'] ?? '',
$_SERVER['HTTP_CLICKFIX_WEBHOOK_SIGNATURE'] ?? '',
$raw,
);
if (!$ok) {
http_response_code(401);
exit;
}
// ClickFix-Webhook-Id ile tekilleştirin, kaydedin, sonra 2xx dönün.
$event = json_decode($raw, true);
error_log(json_encode(['received' => $event['event'] ?? null]));
http_response_code(200);# Adım 7: webhook aboneliği (webhooks:write). data.secret yalnız bu yanıtta döner.
curl -sS --fail-with-body -X POST "$CLICKFIX_BASE_URL/v1/public-api/webhooks" \
-H "Authorization: Bearer $CLICKFIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: webhook-crm-$(date +%s)" \
-d '{ "name": "CRM senkronizasyonu", "url": "https://example.com/clickfix/webhook", "events": ["conversation.created", "message.received", "contact.updated"], "enabled": true }'Zapier, Make ve n8n için POST /v1/public-api/subscriptions (REST hook) ve GET /v1/public-api/triggers (imleçli yoklama) aynı altyapıyı kullanır: Zapier, Make ve n8n.
9. Mobil: oturumu taşıyan güvenli webview
Mobil uygulamalar için yol, ayrı bir yerel SDK değil, oturumu taşıyan güvenli bir webview'dir: uygulama https://getclickfix.com/widget-embed.html?widget=WIDGET_ID sayfasını bir webview'de açar; widget tam ekran açılır ve uygulamayla köprü üzerinden konuşur. Böylece Flutter, React Native, iOS, Android ve diğer her platform aynı yolla, aynı sözleşmeyle bağlanır. Köprü sözleşmesinin tamamı (mesaj adları, alanlar, hata kodları, güvenlik kuralları) ayrı belgededir: Webview köprüsü.
Oturum devri
- Uygulama, kendi sunucunuzdan
{ user_id, user_hash, company_id }alır (kimlik doğrulama);user_hashkullanıcı ve firma kimliğini birlikte imzalar, anahtar uygulamaya hiç inmez. - Webview
widget-embed.html?widget=WIDGET_ID&locale=tr&theme=autoadresini açar. Widget hazır olunca köprüden{"type":"clickfix:ready", ...}gelir. - Uygulama
{"type":"clickfix:identify","id":"1","user":{"user_id":"1001","user_hash":"…","name":"…","company":{"id":"F-77"}}}gönderir;company.idimzadaki firma kimliğidir (köprü sözleşmesi 1.2,bridge_version1). Cevap{"type":"clickfix:result","id":"1","ok":true}olur. Reddedilirseok:falseveerror.code(WIDGET_IDENTITY_INVALIDgibi) döner; kullanıcı anonim kalır. - Çıkışta
{"type":"clickfix:reset"}gönderin; ziyaretçi unutulur, sonraki açılış yeni ziyaretçidir.
Webview'in çerez ve depolaması oturumu sayfa yenilemeleri arasında taşır; reset göndermeden hesap değiştirmeyin.
Yerel çekirdekle oturum devri (CF-201). Uygulama widget uçlarını webview olmadan da çağırabilir: Web widget → Gelişmiş → Mobil uygulamalar bölümünde platform ve paket adıyla bir uygulama anahtarı oluşturun; @getclickfix/visitor-core paketi mode: "app" ile bu anahtarı X-ClickFix-App-Key ve X-ClickFix-App-Package başlıklarında gönderir, sayfa kaynağı gerekmez. Yerel oturumu webview'e {"type":"clickfix:session","token":client.getToken()} komutuyla devredin; widget de ready, identify ve reset sonrasında clickfix:session mesajıyla kendi belirtecini verir (client.adoptToken). Anahtar yalnızca oluşturulduğunda gösterilir ve panelden iptal edilir; user_hash doğrulaması tarayıcıdakiyle aynıdır.
Köprü: uygulamadan widget'a ve widget'tan uygulamaya
Uygulama → widget (komutlar): clickfix:identify, clickfix:track (uygulama olayını ekran bağlamına ekler), clickfix:open (view ya da conversation), clickfix:close, clickfix:update (dil, tema, profil, öznitelik), clickfix:consent, clickfix:reset, clickfix:get-state. Her komut isteğe bağlı bir id taşır; id varsa widget clickfix:result ile cevap verir. Komut window.ClickFixBridge.receive(json) çağrısıyla ya da sayfaya postMessage ile ulaşır.
Widget → uygulama: clickfix:ready, clickfix:event (her SDK olayı: message, unread, close, conversation:ended…), clickfix:link (dışarı açılacak bağlantı; sistem tarayıcısında açın) ve clickfix:result. Taşıma, platforma göre widget'ın kendisi tarafından seçilir: ReactNativeWebView.postMessage, iOS webkit.messageHandlers.ClickFix, Android ClickFixAndroid arayüzü, Flutter ClickFixFlutter kanalı.
Aşağıdaki uygulama tarafı örnekleri köprüyü kurar, hazır olunca oturumu devreder, uygulama olayı gönderir ve widget'ın close olayında destek ekranını kapatır. Bunlar kılavuzun otomatik testine girmez; kendi projenizde derleyip doğrulayın.
// Paket: clickfix_webview (sdks/flutter/clickfix_webview; ilk sürüme kadar git bağımlılığı). Köprüyü, kaynak
// kilidini ve ClickFixFlutter kanalını paket kurar; siz oturumu devredersiniz.
import 'package:clickfix_webview/clickfix_webview.dart';
import 'package:url_launcher/url_launcher.dart';
final controller = ClickFixController(
widgetId: 'WIDGET_ID',
locale: 'tr',
conversation: conversationUuid, // isteğe bağlı derin bağlantı
);
ClickFixWebView(
controller: controller,
onReady: (_) async {
final s = await api.supportSession(); // { user_id, user_hash } sunucunuzdan
try {
await controller.identify(ClickFixIdentity(
userId: s.userId, userHash: s.userHash, company: ClickFixCompany(id: s.companyId)));
await controller.track('app_screen', properties: {'screen': 'orders'});
} on ClickFixBridgeError catch (e) {
debugPrint('kimlik reddedildi: ${e.code}'); // WIDGET_IDENTITY_INVALID gibi; ziyaretçi anonim kalır
}
},
onUnread: (count) => badge.value = count,
onLink: (link) => launchUrl(link.url, mode: LaunchMode.externalApplication), // yalnız https gelir
onClose: () => Navigator.of(context).pop(),
);
// Çıkışta: controller.reset(); ekran kapanınca: controller.dispose().Derin bağlantı
- Belirli bir konuşmayı açmak: sayfayı
…/widget-embed.html?widget=WIDGET_ID&conversation=KONUSMA_UUIDile açın ya da açık webview'e{"type":"clickfix:open","conversation":"KONUSMA_UUID"}gönderin. Sekme içinview(home,messages,help,new). - Bildirimden konuşmaya: uygulamanızın kendi anlık bildirimi için
message.sentwebhook olayına abone olun; olayconversation_uuidvecontact_external_idtaşır. Bildirimin derin bağlantısı (uygulamam://destek?conversation=…) açılınca webview'i yukarıdaki adresle açın. Okunmamış rozeti için widget'ınunreadolayı (clickfix:event) yeterlidir. - Widget'tan dışarı: yardım makaleleri ve mesajlardaki bağlantılar
clickfix:linkolarak gelir; sistem tarayıcısında açın. Webview içindegetclickfix.comdışına gezinmeye izin vermeyin.
Güvenlik notları
- Kimlik anahtarı uygulamaya girmez.
user_hashsunucunuzdan, kimliği doğrulanmış bir uçtan gelir; uygulamada hesaplanmaz, sabitlenmez. - Kaynak kısıtı: webview yalnız
https://getclickfix.com(ya da size verilen uygulama adresi) yükler; başka adrese gezinme engellenir,clickfix:linkile gelen adres uygulamada yeniden doğrulanıp (yalnızhttps:) sistem tarayıcısında açılır, intent ya da derin bağlantı işleyicisine verilmez. Üçüncü taraf çerez ve paylaşılan çerez depolarını kapatın; dosya ve içerik erişimini (allowFileAccess,allowContentAccess) kapatın. - JS köprüsü:
window.ClickFixBridge.receiveyalnız bu sayfada vardır; sayfa ClickFix'e aittir. Köprü,postMessageile gelen iletileri çerçeve içindeyken yalnız yapılandırılmış üst kaynaktan (boot({ bridge: { origin } })), çerçeve dışındayken yalnız sayfanın kendisinden kabul eder;"*"kabul edilmez. Reddedilen komutlarda ileti içeriği (örneğinuser_hash) ne konsola ne sonuca yazılır. - Çerez ve depolama: oturum webview'in depolamasında kalır; hesap değişiminde ve çıkışta
clickfix:resetgönderin. Cihaz paylaşımlıysa webview depolamasını da temizleyin. - Onay: uygulamanız onay topluyorsa adrese
consent=1ekleyin ya daclickfix:consentgönderin; onaysız hiçbir şey saklanmaz.
Flutter paketi clickfix_webview (sdks/flutter/clickfix_webview; ClickFixWebView + ClickFixController, köprü sözleşmesi v1, kaynak kilidi, derin bağlantı) ilk sürümünde CI'daki sdk-publish işiyle pub.dev'e yayımlanacaktır; o zamana kadar pubspec.yaml içinde git bağımlılığı olarak kullanın: clickfix_webview: { git: { url: https://github.com/ClickSolveMainTR/clickfix-app-go.git, path: sdks/flutter/clickfix_webview, ref: main } }. Yerel Dart istemcisi (clickfix_visitor) ve diğer yerel SDK'lar ayrı işlerdir; çıktıklarında aynı köprü sözleşmesini kullanacaklardır.
Laravel paketi
Laravel 10, 11, 12 ve 13 (PHP 8.1+) için clickfix/laravel bu kılavuzdaki adımları tek pakette toplar: servis sağlayıcı ve ClickFix facade'ı, config/clickfix.php, artisan komutları, imzayı doğrulayan POST /clickfix/webhook rotası ve her olay için bir Laravel event'i, @clickfixWidget Blade direktifi. Paket keşfi sağlayıcıyı kendisi kaydeder. Kaynak ve değişiklik günlüğü: clickfix-laravel.
Kurulum
composer require clickfix/laravel
php artisan clickfix:install
php artisan clickfix:checkclickfix:install config/clickfix.php dosyasını yayımlar, .env dosyasına eksik CLICKFIX_* anahtarlarını ekler (webhook sırrını üretir), abone olunacak webhook adresini ve sonraki adımları yazar. Anahtarları doldurduktan sonra clickfix:check API anahtarını API'ye karşı dener, widget kimliğini, kimlik anahtarını, webhook sırrını ve rotanın https olduğunu doğrular; bir şey eksikse çıkış kodu 1'dir.
.env anahtarı | Nereden |
|---|---|
CLICKFIX_API_KEY | Başlamadan önce tablosundaki API anahtarı |
CLICKFIX_WIDGET_ID | Web widget → Kurulum |
CLICKFIX_IDENTITY_SECRET | Web widget → Güvenlik → Kimlik doğrulama; yalnız sunucuda kalır |
CLICKFIX_WEBHOOK_SECRET | Aboneliğin data.secret değeri ya da clickfix:install'ın ürettiği değer (aboneliğe girin) |
CLICKFIX_WEBHOOK_QUEUE | Boş: olaylar istek içinde; true veya kuyruk adı: kuyruğa alınan işten |
Artisan komutları
| Komut | Ne yapar |
|---|---|
clickfix:install [--force] [--no-env] | Config yayımlar, .env anahtarlarını ekler, webhook sırrı üretir, webhook adresini yazar |
clickfix:check | API anahtarı, widget kimliği, kimlik anahtarı, webhook sırrı ve rotası; hata varsa çıkış kodu 1 |
clickfix:user-hash {user} [--raw] [--json] | Bir kullanıcı (model anahtarı) için user_hash; WIDGET_IDENTITY_INVALID ayıklamak için |
clickfix:webhook:secret [--write] | 64 karakter hex sır; --write .env'e yazar |
clickfix:sync:contacts [--id=*] [--since=] [--chunk=] [--dry-run] | config('clickfix.contacts') içindeki modeli dış kimlikle kişi olarak oluşturur/günceller (PUT /contacts/by-external-id, contacts:write) |
clickfix:sync:contacts için model (App\Models\User), alan eşlemesi (name, email, phone, job_title, locale, timezone → model özniteliği veya noktalı yol) ve özel alan eşlemesi config'dedir. Dış kimlik her zaman modelin birincil anahtarıdır; identify'daki user_id ile aynı değer olduğu için eşitlenen kişi ile widget oturumu tek kişidir (dış kimlik).
Widget ve kimlik doğrulama
@clickfixWidget
</body>Direktif kuyruk parçacığını, ClickFix("boot", {...}) çağrısını ve widget.js etiketini basar. Argümansız kullanımda oturum açmış kullanıcı doğrulanır (user_id model anahtarı, user_hash kimlik anahtarıyla sunucuda hesaplanır, name ve email modelden); kimse oturum açmamışsa widget anonim açılır. @clickfixWidget($musteri) belirli bir modeli, @clickfixWidget(null) her zaman anonim widget'ı, @clickfixWidget(auth()->user(), ['locale' => 'tr']) ek başlatma seçeneklerini verir. Kimliğe giren alanlar clickfix.widget.user ile ya da modelde ProvidesClickFixIdentity arayüzüyle (company, attributes dahil) belirlenir.
Inertia, Livewire veya widget'ı kendisi başlatan bir SPA için kimlik yükünü sunucuda üretip sayfaya paylaşın:
use ClickFix\Laravel\Facades\ClickFix;
ClickFix::userHash($user); // hex HMAC-SHA256(kimlik anahtarı, model anahtarı)
ClickFix::identity($user); // ['user_id' => '42', 'user_hash' => '…', 'name' => …, 'email' => …]Tarayıcıda ClickFix("identify", identity) çağırın; kimlik anahtarı sunucudan çıkmaz (kimlik doğrulama).
Webhook
Paket POST /clickfix/webhook rotasını web grubunun dışında (oturum ve CSRF yok) ve clickfix.webhook middleware'i arkasında kaydeder. Middleware ClickFix-Webhook-Timestamp ve ClickFix-Webhook-Signature başlıklarını ham gövde üzerinde doğrular (Webhook'lar bölümündeki kuralla; 5 dakika tolerans, sabit zamanlı karşılaştırma); bozuk veya bayat imza 401, sır tanımsızsa 503 döner. Aynı ClickFix-Webhook-Id ile gelen tekrar teslimat önbellekten tanınır ve olaylar ikinci kez tetiklenmeden 200 ile yanıtlanır.
Doğrulanan her teslimat ClickFix\Laravel\Events\WebhookReceived olayını, tanınan olay adları ayrıca kendi sınıfını tetikler: ConversationCreated, ConversationResolved, MessageReceived, MessageSent, ContactCreated, ContactUpdated, CompanyUpdated, SlaBreached, CsatSubmitted … (olay başına bir sınıf; tanınmayan ad yalnız WebhookReceived).
use ClickFix\Laravel\Events\MessageReceived;
use Illuminate\Support\Facades\Event;
Event::listen(MessageReceived::class, function (MessageReceived $event) {
$event->get('conversation_uuid');
$event->get('contact_external_id'); // sizin kullanıcı numaranız
$event->payload->occurredAt(); // Carbon
});CLICKFIX_WEBHOOK_QUEUE=true (veya bir kuyruk adı) ile olaylar istek yerine ProcessWebhook işinden tetiklenir ve rota 202 döner. Rotayı kendiniz tanımlamak için CLICKFIX_WEBHOOK_ROUTE=false yapıp kendi rotanıza clickfix.webhook middleware'ini ekleyin. Abonelik açma örneği için Webhook'lar.
Sonraki adımlar
Kurulum sihirbazının kalan adımları kod değil panel işidir:
- AI'a öğret: Yapay zekâ. Sistem bağlamadaki kavramlar ve işler AI tariflerine girer.
- Bilgi bankası: Bilgi bankası. Widget'ın yardım sekmesi ve AI buradan beslenir.
- Otomasyon ve SLA: Otomasyon. İş akışı koşulları özel alanları ve ekran bağlamını okuyabilir; dışarı aksiyon için webhook'lar.
Diğer başvurular: API, SDK'lar ve örnek kod, Web widget, Webhook'lar, API başvurusu.
Görüntülü görüşme odaları
Müşterilerinizle giriş gerektirmeyen, PIN ve bekleme odasıyla korunan, uçtan uca şifrelenebilen görüntülü görüşmeler yapın.
API ve SDK hızlı başlangıç
API anahtarı oluşturun, ClickFix REST API'sine ilk isteğinizi gönderin; yanıt yapısı, sayfalama, idempotency ve hız sınırları.