Webhook-Dokumentation
Vollständige Anleitung zur Integration mit Contento-Webhooks. Empfange automatisch generierte Artikel an deinem Endpunkt.
1. Was sind Webhooks?
Webhooks ermöglichen es Contento, generierte Artikel automatisch über HTTP-POST-Anfragen an beliebige externe Dienste zu senden. Wenn ein Artikel im Dashboard veröffentlicht wird, sendet Contento eine Anfrage mit den Artikeldaten an die von dir konfigurierte URL.
Dies ist eine Alternative zur direkten WordPress-Integration und funktioniert mit jeder Plattform, die HTTP-Anfragen empfangen kann: eigene Websites, CMS, Automatisierungssysteme (Zapier, Make, n8n) oder dein eigenes Backend.
2. Konfiguration
Für die Webhook-Integration benötigst du:
- Name der Integration — ein beschreibender Name (z. B. “Meine Website”, “WordPress-Blog”)
- Webhook-URL — die HTTPS-Adresse, an die Contento Artikel per POST sendet
- Zugriffstoken (optional) — ein gemeinsames Secret zur Authentifizierung der Anfragen
Den Webhook kannst du unter Einstellungen → Integrationen → Webhook im Contento-Dashboard konfigurieren.
3. Authentifizierung
Wenn du ein Zugriffstoken konfiguriert hast, sendet Contento es im Authorization-Header jeder Anfrage:
Authorization: Bearer <dein-token>
Es wird empfohlen, dieses Token in deinem Endpunkt zu prüfen, um sicherzustellen, dass die Anfrage von Contento stammt. Beispiel in Node.js:
function validateRequest(req) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return false;
}
const token = authHeader.split(' ')[1];
return token === process.env.CONTENTO_WEBHOOK_TOKEN;
}
4. Payload-Struktur
Wenn du einen Artikel veröffentlichst, sendet Contento eine POST-Anfrage mit dem Header Content-Type: application/json und folgender JSON-Struktur:
{
"timestamp": "2026-02-13T15:45:30Z",
"data": {
"articles": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Titel des Artikels",
"content_markdown": "# Titel des Artikels\n\nInhalt im Markdown-Format...",
"content_html": "<h1>Titel des Artikels</h1><p>Inhalt im HTML-Format...</p>",
"meta_description": "SEO-Meta-Beschreibung des Artikels",
"created_at": "2026-02-10T10:20:30Z",
"slug": "titel-des-artikels",
"image_url": "https://example.com/storage/bild.jpg",
"tags": ["seo-tipps", "digital-marketing"]
}
]
}
}
5. Artikelfelder
| Feld | Typ | Beschreibung |
|---|---|---|
id |
string (UUID) | Eindeutige Kennung des Artikels |
title |
string | Titel des Artikels |
content_markdown |
string | Artikelinhalt im Markdown-Format |
content_html |
string | Artikelinhalt im HTML-Format |
meta_description |
string | SEO-Meta-Beschreibung des Artikels |
created_at |
string (ISO 8601) | Erstellungsdatum und -uhrzeit des Artikels |
slug |
string | URL-freundlicher Slug des Artikels |
image_url |
string oder null | URL des Hauptbildes (falls vorhanden) |
tags |
array of strings | Relevante Tags, automatisch aus der Sitemap extrahiert (max. 5, kann leer sein) |
6. Verbindung testen
Bevor du die Integration speicherst, kannst du die Verbindung über die Schaltfläche “Verbindung testen” auf der Konfigurationsseite prüfen. Contento sendet eine Testanfrage mit folgender Struktur:
{
"timestamp": "2026-02-13T15:45:30Z",
"data": {
"verify": true
}
}
Dein Endpunkt muss mit einem HTTP-Status 2xx antworten, damit der Test als erfolgreich gilt.
7. Endpunkt implementieren
Dein Endpunkt muss:
- POST-Anfragen mit
Content-Type: application/jsonakzeptieren - Das Zugriffstoken im
Authorization-Header prüfen (falls konfiguriert) - Einen HTTP-Status
2xx(z. B. 200, 201) zurückgeben, um den Empfang zu bestätigen - Innerhalb von maximal 30 Sekunden antworten (sonst läuft die Anfrage ab)
const express = require('express');
const app = express();
app.use(express.json());
const WEBHOOK_TOKEN = process.env.CONTENTO_WEBHOOK_TOKEN;
app.post('/contento/webhook', (req, res) => {
// Zugriffstoken prüfen
const authHeader = req.headers['authorization'];
if (WEBHOOK_TOKEN && (!authHeader || authHeader !== `Bearer ${WEBHOOK_TOKEN}`)) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Verbindungstest
if (req.body.data?.verify) {
return res.status(200).json({ status: 'ok' });
}
// Artikel verarbeiten
const { articles } = req.body.data;
articles.forEach(article => {
console.log(`Artikel empfangen: ${article.title}`);
console.log(`Slug: ${article.slug}`);
console.log(`HTML: ${article.content_html.substring(0, 100)}...`);
// Artikel in der Datenbank speichern, auf der Website veröffentlichen usw.
});
res.status(200).json({ status: 'received' });
});
from flask import Flask, request, jsonify
import os
app = Flask(__name__)
WEBHOOK_TOKEN = os.environ.get('CONTENTO_WEBHOOK_TOKEN')
@app.route('/contento/webhook', methods=['POST'])
def webhook():
# Zugriffstoken prüfen
auth_header = request.headers.get('Authorization', '')
if WEBHOOK_TOKEN and auth_header != f'Bearer {WEBHOOK_TOKEN}':
return jsonify({'error': 'Unauthorized'}), 401
payload = request.get_json()
# Verbindungstest
if payload.get('data', {}).get('verify'):
return jsonify({'status': 'ok'}), 200
# Artikel verarbeiten
articles = payload['data']['articles']
for article in articles:
print(f"Artikel empfangen: {article['title']}")
# Artikel in der Datenbank speichern, auf der Website veröffentlichen usw.
return jsonify({'status': 'received'}), 200
<?php
$webhookToken = getenv('CONTENTO_WEBHOOK_TOKEN');
// Zugriffstoken prüfen
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if ($webhookToken && $authHeader !== "Bearer $webhookToken") {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
$payload = json_decode(file_get_contents('php://input'), true);
// Verbindungstest
if (!empty($payload['data']['verify'])) {
http_response_code(200);
echo json_encode(['status' => 'ok']);
exit;
}
// Artikel verarbeiten
foreach ($payload['data']['articles'] as $article) {
error_log("Artikel empfangen: " . $article['title']);
// Artikel in der Datenbank speichern, auf der Website veröffentlichen usw.
}
http_response_code(200);
echo json_encode(['status' => 'received']);
require 'sinatra'
require 'json'
WEBHOOK_TOKEN = ENV['CONTENTO_WEBHOOK_TOKEN']
post '/contento/webhook' do
content_type :json
# Zugriffstoken prüfen
if WEBHOOK_TOKEN
auth_header = request.env['HTTP_AUTHORIZATION'] || ''
unless auth_header == "Bearer #{WEBHOOK_TOKEN}"
halt 401, { error: 'Unauthorized' }.to_json
end
end
payload = JSON.parse(request.body.read)
# Verbindungstest
if payload.dig('data', 'verify')
return { status: 'ok' }.to_json
end
# Artikel verarbeiten
payload['data']['articles'].each do |article|
puts "Artikel empfangen: #{article['title']}"
puts "Slug: #{article['slug']}"
# Artikel in der Datenbank speichern, auf der Website veröffentlichen usw.
end
{ status: 'received' }.to_json
end
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
type Article struct {
ID string `json:"id"`
Title string `json:"title"`
ContentMarkdown string `json:"content_markdown"`
ContentHTML string `json:"content_html"`
MetaDescription string `json:"meta_description"`
CreatedAt string `json:"created_at"`
Slug string `json:"slug"`
ImageURL *string `json:"image_url"`
}
type Payload struct {
Timestamp string `json:"timestamp"`
Data struct {
Verify bool `json:"verify,omitempty"`
Articles []Article `json:"articles,omitempty"`
} `json:"data"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
webhookToken := os.Getenv("CONTENTO_WEBHOOK_TOKEN")
w.Header().Set("Content-Type", "application/json")
// Zugriffstoken prüfen
if webhookToken != "" {
auth := r.Header.Get("Authorization")
if !strings.HasPrefix(auth, "Bearer ") || auth[7:] != webhookToken {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"error": "Unauthorized"})
return
}
}
var payload Payload
json.NewDecoder(r.Body).Decode(&payload)
// Verbindungstest
if payload.Data.Verify {
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
return
}
// Artikel verarbeiten
for _, article := range payload.Data.Articles {
fmt.Printf("Artikel empfangen: %s\n", article.Title)
fmt.Printf("Slug: %s\n", article.Slug)
// Artikel in der Datenbank speichern, auf der Website veröffentlichen usw.
}
json.NewEncoder(w).Encode(map[string]string{"status": "received"})
}
func main() {
http.HandleFunc("/contento/webhook", webhookHandler)
http.ListenAndServe(":8080", nil)
}
8. Häufige Probleme
Wenn der Webhook nicht korrekt funktioniert, prüfe Folgendes:
- URL öffentlich erreichbar — der Endpunkt muss im Internet verfügbar sein, nicht nur lokal
- HTTPS verwenden — wir empfehlen eine gesicherte URL mit gültigem SSL-Zertifikat
- Token stimmt überein — prüfe, ob das in Contento konfigurierte Token identisch mit dem auf dem Server ist (keine Leerzeichen oder zusätzliche Zeichen)
- Endpunkt akzeptiert JSON — stelle sicher, dass der Server Anfragen mit
Content-Type: application/jsonkorrekt verarbeitet - Antwortzeit — der Endpunkt muss innerhalb von maximal 30 Sekunden antworten
- Hosting-Plattformen — wenn du Vercel, Netlify oder eine andere Serverless-Plattform verwendest, stelle sicher, dass die Webhook-Route ohne zusätzliche Authentifizierung öffentlich erreichbar ist
9. Best Practices
- Token immer prüfen — validiere den Authorization-Header, um sicherzustellen, dass die Anfrage von Contento stammt
- Anfragen protokollieren — führe ein Log der empfangenen Webhooks für das Debugging
- Schnell antworten — verarbeite den Artikel asynchron, wenn die Operation länger dauert (z. B. mit einer Message Queue)
- Verfügbarkeit überwachen — richte Alerts ein für den Fall, dass der Endpunkt nicht erreichbar ist
- Error Handling — implementiere ein Fehlerbehandlungskonzept im Endpunkt, um den Verlust von Artikeln zu verhindern
10. Kontakt
Bei Fragen oder wenn du Hilfe bei der Webhook-Integration benötigst, kontaktiere uns unter [email protected].