Webhook: tamamlanan yazı imzalı bir istekle sisteminize gelir
WordPress dışındaki siteler, kendi yayın akışınız ya da otomasyon araçları için. Betig, yazı tamamlandığında belirlediğiniz adrese JSON gövdeli bir POST isteği gönderir. İstek Standard Webhooks biçiminde imzalanır; sahte istekleri kolayca ayırırsınız.
Üç başlık, bir gövde
webhook-idOlayın benzersiz kimliği. Aynı olay yeniden gönderilirse aynı kalır; yinelenenleri ayıklamak için saklayın.webhook-timestampGönderim zamanı, Unix saniyesi. Eski istekleri reddetmek için kullanın (ör. 5 dakika tolerans).webhook-signaturev1,ile başlayan, Base64 kodlu HMAC-SHA256 imzası. Birden fazla imza boşlukla ayrılmış gelebilir (anahtar yenilerken).
POST /webhooks/betig HTTP/1.1
Content-Type: application/json
webhook-id: msg_2b7Qz9yXfJ4kT1
webhook-timestamp: 1791633600
webhook-signature: v1,8axkxMcw4LOYGkdY7FftA629B+AGRGX2FibkqePZ8lE=document.publish olayı
Gövde, Standard Webhooks önerisindeki type, timestamp ve data düzenini izler.1 data.status her zaman "draft" gelir: yazıyı taslak olarak kaydedin, yayından önce bir kişi okusun.
sourcesveverification: kaynak listesi ve doğrulama notu.parent_idvelanguage: çevirilerde kaynak yazının kimliği ve dil kodu (hreflang kurulumu için).schema_jsonld: Article ve FAQPage yapılandırılmış verisi.
{
"type": "document.publish",
"timestamp": "2026-10-10T12:00:00Z",
"data": {
"document_id": "8f0c6a2e-2d0b-4c57-9a51-5b0f3c6a1e77",
"project_id": "1b9e4d33-7c2a-4f0e-8d6b-2a4c9e0f5d12",
"parent_id": null,
"language": "tr",
"status": "draft",
"title": "Garanti süresi kaç yıl? Arızalı elektronik üründe haklarınız",
"slug": "garanti-suresi-kac-yil",
"target_query": "garanti süresi kaç yıl",
"meta": {
"title": "Garanti Süresi Kaç Yıl? Arızalı Üründe Haklarınız",
"description": "Garanti süresi teslimden başlar ve en az iki yıldır. Arızalı üründe dört hakkınızı ve onarım süresini kaynağıyla öğrenin."
},
"article_html": "<p>Tüketiciye satılan mallarda garanti süresi...</p>",
"article_md": "Tüketiciye satılan mallarda garanti süresi...",
"faq": [
{ "q": "Garanti süresi ne zaman başlar?", "a": "Malın teslim tarihinden itibaren başlar." }
],
"schema_jsonld": [
{ "@context": "https://schema.org", "@type": "Article", "headline": "Garanti süresi kaç yıl?" }
],
"sources": [
{
"id": "1",
"title": "6502 sayılı Tüketicinin Korunması Hakkında Kanun",
"url": "https://www.mevzuat.gov.tr/mevzuat?MevzuatNo=6502&MevzuatTur=1&MevzuatTertip=5",
"publisher": "Mevzuat Bilgi Sistemi",
"date": "2013-11-28"
}
],
"verification": [
{ "claim": "Garanti süresi teslimden başlar, en az iki yıldır", "source_ids": ["1"], "status": "birebir", "note": "" }
],
"word_count": 1342
}
}İmzayı doğrulayın
İmzalanan metin webhook-id, webhook-timestamp ve ham gövdenin noktayla birleştirilmesidir: id.timestamp.gövde. Anahtar, panelde gösterilen whsec_ önekli gizli anahtarın önek sonrası Base64 çözülmüş halidir.1 Gövdeyi JSON'a çevirip yeniden yazarsanız imza tutmaz; ham gövdeyle doğrulayın. Standard Webhooks'un Node.js, Python, PHP ve başka diller için hazır kütüphaneleri de kullanılabilir.
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.BETIG_WEBHOOK_SECRET; // whsec_ ile başlar
const TOLERANCE = 5 * 60; // saniye
function verify(rawBody, headers) {
const id = headers["webhook-id"];
const ts = headers["webhook-timestamp"];
const sigHeader = headers["webhook-signature"];
if (!id || !ts || !sigHeader) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > TOLERANCE) return false;
const key = Buffer.from(SECRET.replace(/^whsec_/, ""), "base64");
const expected = crypto
.createHmac("sha256", key)
.update(`${id}.${ts}.${rawBody}`)
.digest("base64");
return sigHeader.split(" ").some((part) => {
const [version, sig] = part.split(",");
if (version !== "v1" || !sig) return false;
const a = Buffer.from(sig);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
});
}
const app = express();
// Gövdeyi ham haliyle alın; JSON'a çevirip geri yazmak imzayı bozar.
app.post("/webhooks/betig", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verify(raw, req.headers)) return res.status(401).end();
const event = JSON.parse(raw);
// event.data.status her zaman "draft": yazıyı taslak olarak kaydedin.
res.status(204).end();
});<?php
function betig_verify(string $body, array $h, string $secret): bool {
$id = $h['HTTP_WEBHOOK_ID'] ?? '';
$ts = $h['HTTP_WEBHOOK_TIMESTAMP'] ?? '';
$sig = $h['HTTP_WEBHOOK_SIGNATURE'] ?? '';
if ($id === '' || $ts === '' || $sig === '') return false;
if (abs(time() - (int) $ts) > 300) return false;
$key = base64_decode(preg_replace('/^whsec_/', '', $secret));
$expected = base64_encode(hash_hmac('sha256', "$id.$ts.$body", $key, true));
foreach (explode(' ', $sig) as $part) {
[$version, $value] = array_pad(explode(',', $part, 2), 2, '');
if ($version === 'v1' && hash_equals($expected, $value)) return true;
}
return false;
}
$body = file_get_contents('php://input');
if (!betig_verify($body, $_SERVER, getenv('BETIG_WEBHOOK_SECRET'))) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
http_response_code(204);import base64, hashlib, hmac, os, time
SECRET = os.environ["BETIG_WEBHOOK_SECRET"] # whsec_ ile başlar
def verify(body: bytes, headers) -> bool:
msg_id = headers.get("webhook-id", "")
ts = headers.get("webhook-timestamp", "")
sig_header = headers.get("webhook-signature", "")
if not (msg_id and ts and sig_header):
return False
if abs(time.time() - int(ts)) > 300:
return False
key = base64.b64decode(SECRET.removeprefix("whsec_"))
signed = f"{msg_id}.{ts}.".encode() + body
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
for part in sig_header.split():
version, _, sig = part.partition(",")
if version == "v1" and hmac.compare_digest(sig, expected):
return True
return FalseTest değeri
Kodunuzu bu sabit değerlerle sınayın; zaman toleransı kontrolünü sınama sırasında kapatın.
Gizli anahtar : whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
webhook-id : msg_2b7Qz9yXfJ4kT1
webhook-timestamp : 1791633600
Gövde : {"type":"document.publish","timestamp":"2026-10-10T12:00:00Z","data":{"document_id":"8f0c6a2e-2d0b-4c57-9a51-5b0f3c6a1e77","status":"draft"}}
İmzalanan : msg_2b7Qz9yXfJ4kT1.1791633600.<gövde>
Beklenen imza : v1,8axkxMcw4LOYGkdY7FftA629B+AGRGX2FibkqePZ8lE=İsteğe nasıl yanıt verilir
- İsteği aldığınızı 2xx kodlu bir yanıtla hemen bildirin; uzun işleri kuyruğa alın. 2xx dışındaki yanıtlar ve zaman aşımı başarısız teslim sayılır.1
- Aynı olay birden fazla kez ulaşabilir.
webhook-iddeğerini saklayın ve ikinci kez işlemeyin. - Gizli anahtarı yalnız sunucu ortamında tutun; tarayıcı koduna ya da depoya koymayın.
API ile sipariş verin
API Profesyonel ve Ajans planlarında açıktır. API anahtarını panelde oluşturursunuz; anahtar yalnız bir kez gösterilir. İstekler Authorization: Bearer başlığıyla gönderilir. Gövde, paneldeki yazı formuyla aynı alanları taşır: başlık, anahtar kelime ya da brief, uzunluk, SSS, brief onayı ve çeviri dilleri.
POST /api/v1/articlesYazı siparişiGET /api/v1/jobs/{id}İşin durumu ve adımlarıGET /api/v1/documents/{id}Yazı, meta, SSS, şema, kaynaklar ve doğrulama notu
curl -X POST https://app.betig.app/api/v1/articles \
-H "Authorization: Bearer $BETIG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"project_id": "1b9e4d33-7c2a-4f0e-8d6b-2a4c9e0f5d12",
"kind": "article",
"input": {
"keyword": "garanti süresi kaç yıl",
"length": "medium",
"include_faq": true,
"review_brief": false
},
"options": {}
}'İlk blog yazınızı kendi konunuzda görün
Hesap açmak ücretsizdir ve kart bilgisi istemez. Araçları ve site önizlemesini hemen kullanırsınız; ödemeli planlar çok yakında açılıyor. Her işin kaç yazı hakkı kullanacağı başlamadan gösterilir.