Partner uygulamaları üç kimlik kanalı kullanır: sunucunuzun Tecof API'sine eriştiği kurulum token'ı, panelin iframe arayüzünüze verdiği oturum token'ı ve Tecof'un size gönderdiği isteklerdeki imza. Üçünün de anahtarı istemci sırrınızdır (tcfs_…); bu yüzden sır yalnız sunucuda durur.
Kimlik Bilgileri#
| Kimlik | Biçim | Ömür | Not |
|---|---|---|---|
| İstemci kimliği | tcfa_… | Kalıcı | Herkese açık; tarayıcı koduna girebilir |
| İstemci sırrı | tcfs_… | Döndürülene kadar | Yalnız uygulama oluşturma ve sır döndürme yanıtında bir kez gösterilir; Tecof tarafında şifreli saklanır |
| Kurulum token'ı | tcfi_… | 1 saat | Bir kuruluma (mağaza + uygulama) bağlıdır; /api/v1/* için Bearer |
| Oturum token'ı | JWT, HS256 | 60 saniye | Panel → iframe kimlik kanıtı; anahtarı istemci sırrı |
| Geliştirici (CLI) token'ı | tcfd_… | Cihaz oturumu 30 gün (14 gün kullanılmazsa düşer); kapsamlı CI token'ı 1–365 gün | CLI, /api/developer uçları ve alanlar (Spaces); bkz. Güvenlik ve CI |
Kapsamlar#
Mağaza, kurulum onay ekranında uygulamanın istediği kapsamların tamamını görür ve onaylar. Uygulamaya açık kapsamlar Developer API kapsamlarının alt kümesidir:
| Kapsam | Ne verir |
|---|---|
orders:read | Sipariş okuma (/api/v1/orders), order.* webhook'ları, sipariş ekranı aksiyonları, sipariş metafield'ları |
orders:write | Sipariş yazma işlemleri (Tools API araçları) |
customers:read | Müşteri okuma (/api/v1/customers), customer.* webhook'ları, müşteri ekranı aksiyonları, müşteri metafield'ları |
customers:write | Müşteri yazma işlemleri |
products:read | Ürün ve envanter okuma (/api/v1/products, /api/v1/inventory), product.*, inventory.*, review.* webhook'ları, ürün ekranı aksiyonları, ürün metafield'ları |
products:write | Ürün yazma; envanter dahil (ör. apply_stock_count aracı). status: "active" ürün anında vitrine çıkar |
marketing:read | Pazarlama verisi okuma, subscriber.created webhook'u |
marketing:write | Pazarlama yazma işlemleri |
settings:read / settings:write | Mağaza ayarlarını okuma / yazma |
cms:read | CMS koleksiyon ve içerik okuma, cms.item.published ve form.submitted webhook'ları |
cms:write | CMS içerik yazma (taslak) |
pages:read / pages:write | Sayfa okuma / taslak yazma |
media:read / media:write | Medya kütüphanesi okuma / dosya ekleme |
analytics:read | Analitik ve mağaza özeti okuma |
domains:read | Alan adı ve DNS kayıtlarını okuma |
Uygulamaya kapalı kapsamlar#
Aşağıdaki kapsamlar uygulamalara verilemez; tecof.app.json içinde istenirse yapılandırma reddedilir. Bunlar ya canlı siteyi doğrudan değiştirir ya da kredi harcar ya da mağaza sahibinin kişisel asistan verisidir.
| Kapsam | Developer API'deki etkisi |
|---|---|
code:read, code:write | Tema deposu okuma/yazma; push üretim yayınını tetikler |
sandbox:run | Bulut IDE'de kod çalıştırma; kredi düşer |
deploy:write | Üretim yeniden dağıtımı, canlı temayı değiştirme |
memory:read, memory:write | Mağaza sahibinin asistana yazdığı kalıcı talimatlar |
domains:write | DNS ve nameserver değişikliği; canlı siteyi etkiler |
ai:generate | AI görsel üretimi; kredi düşer |
Kapsam istemeyen veriler#
Uygulamanın kendi verisi kapsam istemez; kurulumun kendisi yetkidir:
- Ayarlar (
/api/v1/app/settings) - Sırlar / Vault (
/api/v1/app/secrets) merchantsahipli metafield'lar (diğer sahip tipleri, kaynağın okuma kapsamını ister)- Uygulama olayları (
/api/v1/app/events)
Kapsam değişikliği#
- Artış: Yeni yapılandırma ek kapsam isterse mevcut kurulumlara
pendingScopesyazılır. Panel "Ek izin istiyor" uyarısı gösterir; mağaza onaylayana kadar kurulum eski kapsamla çalışır. OnaydagrantedScopesgüncellenir, kurulumun token'ları iptal edilir veapp.scopes_updatedwebhook'u gönderilir. Yayındaki uygulamada kapsam artışı yeniden inceleme ister. - Azalma: Anında uygulanır.
Yetersiz kapsamla yapılan çağrı 403 insufficient-scope döner; data alanı { required: [...], missing: [...] } içerir.
Kurulum Token'ı (Sunucu → Tecof API)#
Sunucunuz, istemci kimliği ve sırrıyla bir kurulum için kısa ömürlü erişim token'ı alır (OAuth 2.0 client_credentials benzeri). Kullanıcıdan bağımsızdır: webhook işleyicilerinde ve arka plan işlerinde de çalışır.
İstek#
curl -X POST https://api.tecof.com/api/apps/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "tcfa_xxxxxxxxxxxxxxxx",
"client_secret": "tcfs_xxxxxxxxxxxxxxxx",
"installation_id": "6720aa00bb11cc22dd33ee55"
}'application/x-www-form-urlencoded gövde de kabul edilir. installation_id yerine merchant_id verilebilir; bu durumda o mağazadaki bu uygulamanın kurulumu kullanılır.
Yanıt#
{
"access_token": "tcfi_xxxxxxxxxxxxxxxx",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "orders:read customers:read",
"installation_id": "6720aa00bb11cc22dd33ee55",
"merchant_id": "66f0c0ffee0000000000abcd"
}Hatalar#
Hatalar OAuth biçimindedir:
| HTTP | Gövde | Neden |
|---|---|---|
| 400 | { "error": "invalid_request" } | Eksik ya da hatalı alan |
| 401 | { "error": "invalid_client" } | İstemci kimliği/sırrı yanlış |
| 400 | { "error": "invalid_grant", "error_description": "installation-not-found" } | Kurulum yok ya da bu uygulamaya ait değil |
| 400 | { "error": "invalid_grant", "error_description": "installation-inactive" } | Kurulum aktif değil (kaldırılmış, askıda…) |
Kurallar#
- Token 1 saat geçerlidir. Kapsam değiştiğinde ya da uygulama askıya alındığında anında iptal edilir. Kaldırmada token'lar
app.uninstalledteslimatı bittikten sonra iptal edilir; bu sayede kaldırma işleyiciniz API'yi hâlâ çağırabilir. - Token Tecof'ta sha256 özeti olarak saklanır; token isteği ve yanıtı istek günlüklerine yazılmaz.
installation_iddeğerini şuralardan alırsınız:app.installedwebhook'u, her webhook'takiX-Tecof-Installation-Idbaşlığı, doğrulanmış oturum token'ındakiiididdiası.- Uygulama token'ında paket özelliği kapısı (
apiAccess) uygulanmaz: uygulama erişimi mağazanın paketine bağlı değildir. - Hız sınırı kurulum başınadır: okuma 240/dk, yazma 60/dk.
/api/v1uçlarında geçersiz token401döner;messageCode:token-invalid,token-revoked,token-expiredya daapp-suspended(uygulama askıda). Ayrıntılar: API ile Veri Erişimi.
SDK ile#
SDK token'ı alır, önbelleğe alır ve süresi dolmadan yeniler:
import { tecof } from "@/lib/tecof";
const api = tecof.client(installationId);
const orders = await api.orders.list({ paymentStatus: "paid", limit: 50 });Oturum Token'ı (Panel → iframe)#
Panel, gömülü uygulamanızı açarken kurulum için 60 saniyelik bir JWT basar. iframe arayüzünüz bu token'ı kendi sunucunuza Authorization: Bearer olarak gönderir; sunucunuz istemci sırrınızla ek bir çağrı yapmadan doğrular.
İddialar (claims)#
{
"iss": "https://api.tecof.com",
"aud": "tcfa_xxxxxxxxxxxxxxxx",
"sub": "66f0aa00bb11cc22dd33ee66",
"mid": "66f0c0ffee0000000000abcd",
"iid": "6720aa00bb11cc22dd33ee55",
"locale": "tr",
"jti": "a1b2c3d4",
"iat": 1760000000,
"nbf": 1759999995,
"exp": 1760000060
}| İddia | Anlamı |
|---|---|
iss | https://api.tecof.com |
aud | Uygulamanızın istemci kimliği |
sub | Paneli kullanan kullanıcının kimliği |
mid | Mağaza kimliği |
iid | Kurulum kimliği |
locale | Panel dili |
jti | Token kimliği |
iat / nbf / exp | Basım zamanı / iat - 5 / iat + 60 |
Başlık: { "alg": "HS256", "typ": "JWT" }.
Açılış (launch) adresi#
Panel uygulamayı ilk kez yüklerken iframe adresini şöyle kurar: appUrl + path + sorgu dizesi. Test mağazasında devUrl (CLI tüneli) appUrl'in yerine geçer.
https://isbasi.apps.tecof.site/invoices?installationId=6720aa00bb11cc22dd33ee55&merchantId=66f0c0ffee0000000000abcd&locale=tr&embedded=1&host=aHR0cHM6Ly9hcHAudGVjb2YuY29t&session=eyJhbGciOiJIUzI1NiIs...| Parametre | Anlamı |
|---|---|
installationId | Kurulum kimliği |
merchantId | Mağaza kimliği |
locale | Panel dili |
embedded | 1 — panel içinde açıldı |
host | Panel origin'inin base64url kodlaması; App Bridge yalnız bu origin'den gelen mesajı kabul eder |
session | İlk oturum token'ı |
actionRunId, actionKey, placement, ids | Yalnız iframe aksiyonlarında. Bkz. Aksiyonlar |
İlk token 60 saniyede dolar. Sonraki istekler için App Bridge getSessionToken() ile yeni token alın; SDK bitişe 10 saniye kala kendiliğinden yeniler. Bkz. App Bridge.
Oturum token'ı doğrulama kuralları#
- İmza HS256, anahtar istemci sırrı. Panel token'ı her zaman güncel sırla imzalar; sır döndürmeden sonraki 24 saat boyunca doğrulamada eski sır da denenir (döndürme anından hemen önce basılmış token'lar için).
aud= istemci kimliğiniz.expvenbf±10 saniye toleransla.iss=https://api.tecof.com.
SDK ile doğrulama#
// app/api/orders/route.ts
import { TecofAuthError } from "@tecof/app-sdk";
import { tecof } from "@/lib/tecof";
export async function GET(req: Request) {
let session;
try {
// Authorization: Bearer <oturum token'ı>
session = tecof.sessionFromRequest(req);
} catch (err) {
if (err instanceof TecofAuthError) {
return Response.json({ error: "unauthorized" }, { status: 401 });
}
throw err;
}
// session = { merchantId, installationId, userId, locale, exp }
const api = tecof.client(session.installationId);
const orders = await api.orders.list({ limit: 20 });
return Response.json({ orders });
}Token'ı başka bir kaynaktan aldıysanız tecof.verifySessionToken(token) aynı iddiaları döner ya da TecofAuthError fırlatır.
Oturum token'ını elle doğrulama (Node.js)#
SDK kullanmayan bir sunucu için eşdeğer doğrulama:
import { createHmac, timingSafeEqual } from "node:crypto";
type SessionClaims = {
iss: string; aud: string; sub: string; mid: string; iid: string;
locale: string; jti: string; iat: number; nbf: number; exp: number;
};
function b64urlToBuffer(input: string): Buffer {
return Buffer.from(input.replace(/-/g, "+").replace(/_/g, "/"), "base64");
}
export function verifyTecofSessionToken(
token: string,
opts: { clientId: string; secrets: string[]; leewaySec?: number },
): SessionClaims {
const leeway = opts.leewaySec ?? 10;
const parts = token.split(".");
if (parts.length !== 3) throw new Error("malformed-token");
const [headerB64, payloadB64, signatureB64] = parts;
const header = JSON.parse(b64urlToBuffer(headerB64).toString("utf8"));
if (header.alg !== "HS256") throw new Error("unsupported-alg");
const signature = b64urlToBuffer(signatureB64);
const signed = `${headerB64}.${payloadB64}`;
// secrets: [güncel sır, (döndürmeden sonraki 24 saat içinde) eski sır]
const valid = opts.secrets.some((secret) => {
const expected = createHmac("sha256", secret).update(signed).digest();
return expected.length === signature.length && timingSafeEqual(expected, signature);
});
if (!valid) throw new Error("invalid-signature");
const claims = JSON.parse(b64urlToBuffer(payloadB64).toString("utf8")) as SessionClaims;
const now = Math.floor(Date.now() / 1000);
if (claims.aud !== opts.clientId) throw new Error("invalid-audience");
if (claims.iss !== "https://api.tecof.com") throw new Error("invalid-issuer");
if (now > claims.exp + leeway) throw new Error("token-expired");
if (now < claims.nbf - leeway) throw new Error("token-not-yet-valid");
return claims;
}İmzalı İstekler (Tecof → Uygulama)#
Webhook teslimatları ve API aksiyonu çağrıları aynı imzayı taşır:
X-Tecof-Timestamp: 1760000000
X-Tecof-Signature: t=1760000000,v1=5f2c…e91av1 = HEX( HMAC_SHA256( clientSecret, `${t}.${rawBody}` ) )Sır döndürmeden sonraki 24 saat boyunca başlık iki imza taşır: t=…,v1=<yeni sırla>,v1=<eski sırla>.
İmza doğrulama kuralları#
tdeğeri ile şimdiki zaman arasındaki fark en fazla 300 saniye olmalı (tekrar saldırısı penceresi).- Başlıktaki
v1değerlerinden herhangi biri beklenen imzayla eşleşirse istek geçerlidir. - Karşılaştırma sabit zamanlı yapılmalıdır (
timingSafeEqual). - İmza ham gövde üzerinden hesaplanır. Next.js'te
await req.text()kullanın;req.json()ile okuyup yeniden serileştirmek imzayı bozar.
SDK ile (tek satır)#
const event = tecof.verifyWebhook(rawBody, req.headers); // webhook
const action = tecof.verifyActionRequest(rawBody, req.headers); // API aksiyonuİkisi de imza geçersizse ya da zaman damgası pencere dışındaysa hata fırlatır.
İmzayı elle doğrulama (Node.js)#
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyTecofSignature(
rawBody: string,
signatureHeader: string | null,
secrets: string[], // [güncel sır, (24 saat içinde) eski sır]
toleranceSec = 300,
): boolean {
if (!signatureHeader) return false;
const parts = signatureHeader.split(",").map((p) => p.trim());
const t = parts.find((p) => p.startsWith("t="))?.slice(2);
const candidates = parts.filter((p) => p.startsWith("v1=")).map((p) => p.slice(3));
if (!t || candidates.length === 0) return false;
const ts = Number(t);
const now = Math.floor(Date.now() / 1000);
if (!Number.isFinite(ts) || Math.abs(now - ts) > toleranceSec) return false;
for (const secret of secrets) {
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
for (const candidate of candidates) {
const given = Buffer.from(candidate, "hex");
if (given.length === expected.length && timingSafeEqual(given, expected)) return true;
}
}
return false;
}
// Kullanım (Next.js route handler):
// const raw = await req.text();
// const ok = verifyTecofSignature(raw, req.headers.get("x-tecof-signature"), [process.env.TECOF_CLIENT_SECRET!]);Terminalde imza üretmek#
Yerel testte aynı imzayı openssl ile üretebilirsiniz:
T=$(date +%s)
BODY='{"id":"evt_test","event":"order.paid","test":true}'
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$TECOF_CLIENT_SECRET" | awk '{print $NF}')
echo "X-Tecof-Signature: t=$T,v1=$SIG"Sır Döndürme#
tecof app secret rotateya da POST /api/developer/apps/:id/secret/rotate → { clientSecret, previousValidUntil }.
Döndürmeden sonraki 24 saat boyunca:
| Kanal | Eski sırrın durumu |
|---|---|
Token ucu (/api/apps/oauth/token) | Eski sır da kabul edilir |
| Webhook / API aksiyonu imzası | Başlık iki v1 taşır (yeni + eski) |
| Oturum token'ı | Panel geçiş boyunca eski sırla imzalar (yeni sırra henüz geçmemiş sunucu da doğrular); 24 saat sonra yeni sırla. SDK her iki sırrı da dener (previousClientSecret) |
- Tecof barındırması: Vercel ortam değişkenleri güncellenir (
TECOF_CLIENT_SECRET= yeni sır,TECOF_PREVIOUS_CLIENT_SECRET=eski_sır|bitiş_zamanı) ve yeniden dağıtım kendiliğinden tetiklenir. SDK eski sırrı bitiş zamanından sonra kendiliğinden yok sayar. - Kendi sunucunuz: Yeni sırrı
TECOF_CLIENT_SECRET'a, eski sırrı geçiş süresinceTECOF_PREVIOUS_CLIENT_SECRET'a yazın (isterseniztcfs_…|2026-10-11T20:00:00Zbiçiminde bitişle — SDK bitişten sonra yok sayar); 24 saat içinde dağıtın. Geçiş boyunca oturum token'ları eski sırla imzalandığı için yeni sırra geçene kadar arayüz kesintisiz çalışır.
export const tecof = new TecofApp({
clientId: process.env.TECOF_CLIENT_ID!,
clientSecret: process.env.TECOF_CLIENT_SECRET!, // yeni (güncel) sır
previousClientSecret: process.env.TECOF_PREVIOUS_CLIENT_SECRET, // isteğe bağlı: 24 saatlik geçişte eski sır
});Güvenlik Kontrol Listesi#
- İstemci sırrı yalnız sunucu ortam değişkeninde;
NEXT_PUBLIC_önekli değişkende, repoda, istemci paketinde yok. - Her webhook ve API aksiyonu isteği ham gövde üzerinden imza + zaman damgası ile doğrulanıyor.
- Yetki kararları sorgu dizesine göre değil, doğrulanmış oturum token'ının
mid/iiddeğerlerine göre veriliyor. - Oturum token'ı ve kurulum token'ı günlüğe, URL'e, hata mesajına yazılmıyor.
- Yalnız gerçekten gereken kapsamlar isteniyor.
- Mağazadan panel parolası, panel JWT'si ya da mağaza
secretKey'i istenmiyor; platform bunları uygulamalara vermez. - Üçüncü taraf API anahtarları (ör. muhasebe yazılımı parolası) Vault'ta saklanıyor. Bkz. Vault.
-
app.uninstalledgeldiğinde o kuruluma ait kendi kopyalarınız siliniyor; teslimattan sonra Tecof token'ları iptal eder, ayarları ve sırları siler. - App Bridge mesajları yalnız
hostparametresindeki origin'le alınıp veriliyor (SDK bunu yapar). - Sır döndürme prosedürü (24 saatlik geçiş) belgelenmiş.