Ana sayfa
Webhook entegrasyonu

Webhook ile sitenize otomatik yayın

Seomio ürettiği içeriği sizin belirlediğiniz tek bir adrese gönderir; siteniz bu isteği alıp yazıyı yayınlar, günceller veya siler. WordPress/Shopify gibi hazır entegrasyonlar dışındaki tüm sistemler için doğru yol budur.

Künye

Yön
Seomio → Siteniz (push)
Metot
POST (create / update / delete)
Kimlik
Authorization: Bearer <API Key>
İçerik tipi
application/json

Nasıl çalışır

Webhook push modelidir: Seomio içeriği ürettiği anda sizin endpoint'inize bir POST isteği gönderir. Siz bir şey sormazsınız, içerik size gelir. Sitenizin yapması gereken tek şey bu isteği karşılayacak tek bir adres açmaktır.

1

Seomio üretir

İçerik planına göre yazı, kapak görseli, meta etiketler ve JSON-LD hazırlanır.

2

Endpoint'inize gönderir

Tek bir POST isteğiyle tam HTML gövde ve tüm SEO alanları size ulaşır.

3

Siz yayınlarsınız

Kaydı oluşturur, id'yi döndürürsünüz. Sonraki güncelleme ve silmeler bu id ile gelir.

Hangi yöntemi seçmeliyim?

Sitenizde içerik kaydı oluşturabileceğiniz bir yapı varsa (kendi CMS'iniz, headless kurulum, özel panel) webhook en doğrusudur — içerik anında yayına girer. Sitenizde yazma tarafı yoksa ve içerikleri kendiniz çekmek istiyorsanız Seomio API dokümanına bakın.

Kurulum

Panelde üç alan doldurulur, sitenizde tek bir endpoint açılır.

  1. 1

    Sitenizde bir endpoint açın

    JSON gövde kabul eden bir POST adresi yeterlidir. Örnek: https://siteniz.com/seomio-webhook. Aşağıdaki örnek alıcı kodları doğrudan kullanabilirsiniz.
  2. 2

    Kendi API anahtarınızı belirleyin

    Tahmin edilemez uzun bir dize üretin (ör. 32+ karakter). Bu anahtarı endpoint'inizde doğrulayacaksınız — Seomio her istekte Authorization başlığında gönderir.
  3. 3

    Panelde platformu seçin

    Panel → SEO → Ayarlar → Yayın Entegrasyonu bölümünde platform olarak Custom API / Webhook seçin; endpoint adresinizi ve API anahtarınızı girin.
  4. 4

    Kimlik doğrulama biçimini seçin

    Varsayılan Bearer token'dır (Authorization: Bearer <key>). Sisteminiz token'ı öneksiz bekliyorsa Ham token seçeneğini kullanın, aksi halde 401 alırsınız.
  5. 5

    Test isteği gönderin

    Aynı ekrandaki Test İsteği Gönder düğmesi, gerçek yayınla birebir aynı isteği yollar ve sonucu HTTP koduyla birlikte gösterir. Ayrıntı: Test etme.

İstek formatı ve kimlik doğrulama

Tüm istekler tek adrese, POST metoduyla ve application/json içerik tipiyle gider. Yayınlama, güncelleme ve silme ayrımı gövdedeki action alanındadır — ayrı URL veya ayrı metot tanımlamanız gerekmez.

İstek başlıkları
POST /seomio-webhook HTTP/1.1
Host: siteniz.com
Content-Type: application/json
Authorization: Bearer SIZIN_API_KEYINIZ
AlanTipAçıklama
actionstringcreate · update · delete. İşlemin ne olduğunu bu alan söyler.
externalIdstring | nullSizin sisteminizdeki kayıt id'si. create'te null gelir; update ve delete'te create yanıtında döndürdüğünüz id geri gönderilir.
AuthorizationheaderPanelde girdiğiniz API Key. Varsayılan biçim Bearer <key>, "Ham token" seçilirse anahtar öneksiz gönderilir. Boş bırakılırsa başlık hiç eklenmez.
Ek başlıklarheaderEntegrasyon ayarlarındaki özel başlıklar (varsa) her isteğe eklenir.

Yönlendirme (redirect) kullanmayın

Endpoint adresiniz 301/302 ile başka bir adrese yönlendiriyorsa Authorization başlığı yönlendirmede düşer ve istek 401 alır. Panele nihai adresi yazın (www / https farkına dikkat).

Yayınlama — create ve update

Yeni yazı action: "create" ile gelir, externalId boştur. Aynı yazı sonradan güncellenirse action: "update" ve dolu bir externalId ile aynı adrese gönderilir.

POSThttps://siteniz.com/seomio-webhook
create — istek gövdesi
{
  "action": "create",
  "externalId": null,
  "title": "PVC Zemin Bakımı: Dayanıklılığı Nasıl Korursunuz?",
  "slug": "pvc-zemin-bakimi",
  "content": "<div class=\"key-takeaways\">...</div><h2>Giriş</h2><p>...</p>",
  "excerpt": "PVC zeminlerin ömrünü uzatan bakım adımları.",
  "keyword": "pvc zemin bakımı",
  "metaTitle": "PVC Zemin Bakımı | Marka",
  "metaDescription": "PVC zemin bakımı için pratik ve uzman önerileri.",
  "coverImageUrl": "https://cdn.seomio.com.tr/blog-images/cover-8f21.png",
  "publishedDate": "2026-08-19T09:00:00.000Z"
}
AlanTipAçıklama
titlestringYazı başlığı.
slugstringURL için hazırlanmış, harf-rakam-tire biçiminde adres parçası.
contentstring (HTML)Tam HTML gövde. İçinde gövde görselleri (CDN üzerinden mutlak URL), "İlgili İçerikler" bölümü ve JSON-LD yapısal verisi (Article + FAQ + Breadcrumb) gömülü gelir. Olduğu gibi yayınlayın, ayrıştırmaya veya temizlemeye gerek yok.
excerptstringKısa özet — liste ve kart görünümleri için.
keywordstringYazının hedef anahtar kelimesi.
metaTitlestringSayfanın <title> etiketi için önerilen değer.
metaDescriptionstringMeta açıklama.
coverImageUrlstring (URL)Kapak görselinin mutlak CDN adresi. Doğrudan <img src> olarak kullanabilir veya kendi medya kitaplığınıza indirebilirsiniz; erişim için ek kimlik doğrulama gerekmez.
publishedDatestring (ISO 8601)Yayın tarihi.
update — istek gövdesi
{
  "action": "update",
  "externalId": "1421",
  "title": "PVC Zemin Bakımı: 2026 Güncel Rehber",
  "slug": "pvc-zemin-bakimi",
  "content": "<h2>Güncellenmiş içerik</h2><p>...</p>",
  "excerpt": "Güncellenmiş özet.",
  "keyword": "pvc zemin bakımı",
  "metaTitle": "PVC Zemin Bakımı 2026 | Marka",
  "metaDescription": "2026 için güncellenmiş bakım rehberi.",
  "coverImageUrl": "https://cdn.seomio.com.tr/blog-images/cover-8f21.png",
  "publishedDate": "2026-08-19T09:00:00.000Z"
}

update için 404 dönebilirsiniz

Gelen externalId'ye karşılık bir kaydınız kalmadıysa 404 dönün. Seomio bunu görür ve aynı içeriği yeni kayıt olarak (create) yeniden gönderir — yayın kilitlenmez.

Silme — delete

Bir yazı Seomio panelinden silindiğinde (veya arşivlenirken "siteden de kaldır" seçildiğinde) aynı endpoint'e action: "delete" gövdesiyle bir istek gönderilir. Böylece paneldeki silme işlemi sitenize de yansır; elle temizlik yapmanız gerekmez.

POSThttps://siteniz.com/seomio-webhook
delete — istek gövdesi
{
  "action": "delete",
  "externalId": "1421",
  "slug": "pvc-zemin-bakimi",
  "title": "PVC Zemin Bakımı: 2026 Güncel Rehber",
  "deletedDate": "2026-08-19T11:20:00.000Z"
}
AlanTipAçıklama
actionstringHer zaman "delete".
externalIdstringSilinecek kaydı adresleyen tek alan. Create yanıtında döndürdüğünüz id'dir.
slugstring | nullYalnızca log ve eşleştirme kolaylığı için gönderilir.
titlestring | nullYalnızca log ve eşleştirme kolaylığı için gönderilir.
deletedDatestring (ISO 8601)Silme isteğinin gönderildiği an.

Yanıtınız nasıl yorumlanır

HTTPSeomio ne yapar
2xxKaldırıldı sayılır; panelde "sitenizden kaldırıldı" bilgisi görünür.
404 / 410Kayıt sizde zaten yok kabul edilir ve bu da başarı sayılır.
405 / 501Endpoint'in silmeyi desteklemediği anlaşılır; panelde "silme desteği ekleyin" uyarısı çıkar.
Diğer hatalarYazı Seomio'da yine de silinir, panelde "sitenizden kaldırılamadı" notu görünür. İstek tekrarlanmaz.

Silme isteği yayınla aynı adrese gider

Ayrı bir DELETE metodu veya ayrı bir yol beklemeyin. Endpoint'iniz action alanını okumuyorsa, silme isteğini yeni yazı sanıp boş bir kayıt oluşturabilir. Alıcı kodunuzda delete dalını en başa koyun.

Silme senkronunu kapatmak

Yazıların sitenizde kalmasını istiyorsanız entegrasyon ayarlarındaki syncDeletes değerini false yapın — bu durumda silme isteği hiç gönderilmez, yazı yalnızca Seomio panelinden kalkar.
Silme dalını elle sınayın
curl -X POST "https://siteniz.com/seomio-webhook" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer SIZIN_API_KEYINIZ" \
  -d '{
    "action": "delete",
    "externalId": "1421",
    "slug": "pvc-zemin-bakimi"
  }'

Sizden beklenen yanıt

2xx dönen her yanıt başarı sayılır. Kritik olan tek şey: create yanıtında kaydınızın id'sini döndürmeniz.

create yanıtı
{
  "id": "1421"
}
delete yanıtı
{
  "id": "1421",
  "deleted": true
}

id alanı neden bu kadar önemli?

Döndürdüğünüz id, Seomio tarafında yazının externalId'si olarak saklanır ve sonraki güncelleme ve silme isteklerinde size geri gönderilir. Bu alanı döndürmezseniz her güncelleme yeni bir kayıt olarak gelir; sitenizde aynı yazının kopyaları birikir ve silme senkronu hiç çalışmaz.

id, postId veya externalId adlarından herhangi biri kabul edilir.

Örnek alıcı kodlar

Üç dalı da (create / update / delete) karşılayan, doğrudan uyarlanabilir örnekler.

Node.js — Express
const express = require("express");
const app = express();

// İçerik HTML gövdesiyle gelir — varsayılan 100kb limiti yetmeyebilir
app.use(express.json({ limit: "5mb" }));

const SEOMIO_KEY = process.env.SEOMIO_WEBHOOK_KEY;

app.post("/seomio-webhook", async (req, res) => {
  // 1) Kimlik doğrulama — panelde girdiğiniz API Key
  const token = (req.headers.authorization || "").replace(/^Bearer /i, "");
  if (!SEOMIO_KEY || token !== SEOMIO_KEY) {
    return res.status(401).json({ message: "unauthorized" });
  }

  const b = req.body || {};

  // 2) SİLME — Seomio'da silinen yazı sitenizden de kalksın
  if (b.action === "delete") {
    const removed = await Post.deleteBySeomioRef(b.externalId);
    if (!removed) return res.status(404).json({ message: "not found" });
    return res.json({ id: b.externalId, deleted: true });
  }

  // 3) GÜNCELLEME — externalId sizin daha önce döndürdüğünüz id'dir
  if (b.action === "update" && b.externalId) {
    const updated = await Post.update(b.externalId, {
      title: b.title,
      slug: b.slug,
      html: b.content,
      excerpt: b.excerpt,
      metaTitle: b.metaTitle,
      metaDescription: b.metaDescription,
      coverImageUrl: b.coverImageUrl
    });
    if (!updated) return res.status(404).json({ message: "not found" });
    return res.json({ id: b.externalId });
  }

  // 4) YENİ YAZI — yanıtta id DÖNDÜRÜN, Seomio bunu saklar
  const post = await Post.create({
    title: b.title,
    slug: b.slug,
    html: b.content,
    excerpt: b.excerpt,
    metaTitle: b.metaTitle,
    metaDescription: b.metaDescription,
    coverImageUrl: b.coverImageUrl,
    publishedAt: b.publishedDate
  });

  return res.json({ id: String(post.id) });
});

app.listen(3000);
PHP — Laravel controller
<?php
// routes/api.php  —  Route::post('/seomio-webhook', SeomioWebhookController::class);

class SeomioWebhookController extends Controller
{
    public function __invoke(Request $request)
    {
        // 1) Kimlik doğrulama
        $token = str_replace('Bearer ', '', $request->header('Authorization', ''));
        if ($token !== config('services.seomio.webhook_key')) {
            return response()->json(['message' => 'unauthorized'], 401);
        }

        $action = $request->input('action', 'create');
        $externalId = $request->input('externalId');

        // 2) SİLME
        if ($action === 'delete') {
            $post = Post::where('seomio_ref', $externalId)->first();
            if (!$post) {
                return response()->json(['message' => 'not found'], 404);
            }
            $post->delete();
            return response()->json(['id' => $externalId, 'deleted' => true]);
        }

        $data = [
            'title'            => $request->input('title'),
            'slug'             => $request->input('slug'),
            'body'             => $request->input('content'),
            'excerpt'          => $request->input('excerpt'),
            'meta_title'       => $request->input('metaTitle'),
            'meta_description' => $request->input('metaDescription'),
            'cover_url'        => $request->input('coverImageUrl'),
            'published_at'     => $request->input('publishedDate'),
        ];

        // 3) GÜNCELLEME
        if ($action === 'update' && $externalId) {
            $post = Post::find($externalId);
            if (!$post) {
                return response()->json(['message' => 'not found'], 404);
            }
            $post->update($data);
            return response()->json(['id' => (string) $post->id]);
        }

        // 4) YENİ YAZI
        $post = Post::create($data);
        return response()->json(['id' => (string) $post->id]);
    }
}
Next.js — App Router route handler
// app/api/seomio-webhook/route.ts  (Next.js App Router)
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const token = (req.headers.get("authorization") || "").replace(/^Bearer /i, "");
  if (token !== process.env.SEOMIO_WEBHOOK_KEY) {
    return NextResponse.json({ message: "unauthorized" }, { status: 401 });
  }

  const body = await req.json();

  if (body.action === "delete") {
    const removed = await db.post.deleteMany({ where: { seomioRef: body.externalId } });
    if (!removed.count) {
      return NextResponse.json({ message: "not found" }, { status: 404 });
    }
    return NextResponse.json({ id: body.externalId, deleted: true });
  }

  if (body.action === "update" && body.externalId) {
    const post = await db.post.update({
      where: { id: body.externalId },
      data: { title: body.title, slug: body.slug, html: body.content }
    });
    return NextResponse.json({ id: String(post.id) });
  }

  const post = await db.post.create({
    data: {
      title: body.title,
      slug: body.slug,
      html: body.content,
      coverUrl: body.coverImageUrl,
      publishedAt: new Date(body.publishedDate)
    }
  });

  return NextResponse.json({ id: String(post.id) });
}

Gövde limitini yükseltin

İçerik tam HTML olarak gelir ve uzun yazılarda birkaç yüz KB'ı bulabilir. Framework'ünüzün varsayılan gövde limiti (çoğu zaman 100 KB) yetmeyebilir — örneklerdeki gibi 5mb civarına çekin, aksi halde istekler sessizce 413 ile düşer.

Test etme

Panel → SEO → Ayarlar → Yayın Entegrasyonu ekranındaki Test İsteği Gönder düğmesi, gerçek yayınla birebir aynı isteği gönderir. Ayrı bir "test" bayrağı yoktur — bu kasıtlıdır, entegrasyon gerçek koşulda sınanır.

  1. 1

    Test yazısı gönderilir

    Başlığı "Seomio Test Yazısı" olan, lorem ipsum gövdeli gerçek bir içerik yollanır. Endpoint'iniz kabul ederse sitenizde gerçek bir yazı oluşur.
  2. 2

    Sonuç HTTP koduyla gösterilir

    Panelde durum kodu, süre, gönderilen gövde ve aldığınız yanıt görünür; ayrıca yanıtta id dönüp dönmediği ayrıca belirtilir.
  3. 3

    Test yazısını otomatik sildirin

    Test sonrası temizle seçeneği işaretliyse, oluşan test yazısı aynı endpoint'e action: "delete" gönderilerek geri kaldırılır. Böylece tek testte hem yayın hem silme sözleşmesi doğrulanmış olur.

Temizlik için id şart

Test yazısının silinebilmesi için create yanıtınızda id dönmüş olması gerekir. Dönmediyse hangi kaydın silineceği bilinemez ve test yazısı sitenizde kalır.

Güvenlik

Endpoint'iniz internete açık bir yazma ucudur; birkaç basit kural yeterlidir.

  • Anahtarı her istekte doğrulayın. Authorization başlığı beklediğiniz değerle birebir eşleşmiyorsa 401 dönün — silme dalında da aynı kontrol geçerli olmalı.
  • Yalnızca HTTPS kullanın. Anahtar düz metin olarak başlıkta gider.
  • Anahtarı kodda tutmayın. Ortam değişkeninde saklayın; sızdığından şüphelenirseniz panelden yeni bir değer girip endpoint'inizde güncelleyin.
  • İçeriği güvenilir kabul edebilirsiniz ama yine de kendi şablonunuzda çıktı alırken HTML'i beklediğiniz alanlara yerleştirin; site genelinde script çalıştıran alanlara doğrudan enjekte etmeyin.
  • Silme dalını sınırlayın. Yalnızca Seomio'nun oluşturduğu kayıtları silin (kayıtlarınızda bir seomio_ref alanı tutmak en temiz yoldur); böylece yanlış bir externalId sitenizdeki başka bir içeriği silemez.

Sorun giderme

Panelde gördüğünüz duruma göre en olası nedenler.

BelirtiOlası neden ve çözüm
401 / 403Anahtar eşleşmiyor ya da biçim yanlış. Panelde Bearer token Ham token mı seçili kontrol edin; anahtarın başında/sonunda boşluk bırakmayın.
404Endpoint yolu hatalı. Panele yazdığınız adresi tarayıcıda değil, doğrudan curl ile deneyin.
405Adres POST kabul etmiyor. Rotanızı POST olarak tanımlayın; güncelleme ve silme de POST ile gelir.
415Sunucu application/json gövdeyi ayrıştırmıyor. JSON body parser'ı etkinleştirin.
413 / boş gövdeGövde limiti düşük. HTML içerik birkaç yüz KB olabilir, limiti 5mb civarına çıkarın.
Aynı yazı tekrar tekrar oluşuyorCreate yanıtında id döndürmüyorsunuz. Bkz. Beklenen yanıt.
Sildiğim yazı sitede kalıyorEndpoint action: "delete" dalını işlemiyor ya da entegrasyon ayarında syncDeletes: false. Bkz. Silme.
Görseller görünmüyorKapak ve gövde görselleri CDN üzerinde mutlak URL'dir; şablonunuzda göreli yola çevirmeyin.
İstek hiç ulaşmıyorGüvenlik duvarı/WAF Seomio'nun isteğini engelliyor olabilir. Sunucu erişim loglarına bakın; yönlendirme varsa nihai adresi panele yazın.
POSTHâlâ çözemediyseniz: panelde test isteğinin gönderilen gövdesini ve aldığınız yanıtı kopyalayıp bize iletin — aynı isteği birlikte inceleyelim.

Sonraki adım

Seomio API ile içerik çekme

Kendi sitenizden Seomio'ya gelip yayınlanmış içerikleri listelemek isterseniz pull yöntemi.

Takıldığınız yer mi var?

Entegrasyon desteği

Endpoint'inizin aldığı isteği ve döndüğü yanıtı yazın, birlikte bakalım.

info@seomio.com.tr