Documentație Webhooks
Ghid complet pentru integrarea cu webhook-urile Contento. Primește articolele generate automat pe endpoint-ul tău.
1. Ce sunt webhook-urile?
Webhook-urile permit Contento să trimită automat articolele generate către orice serviciu extern prin cereri HTTP POST. Când un articol este publicat din dashboard, Contento trimite un request cu datele articolului către URL-ul pe care l-ai configurat.
Aceasta este o alternativă la integrarea directă cu WordPress și funcționează cu orice platformă care poate primi cereri HTTP: site-uri custom, CMS-uri, sisteme de automatizare (Zapier, Make, n8n) sau propriul tău backend.
2. Configurare
Pentru a configura integrarea webhook ai nevoie de:
- Numele integrării — un nume descriptiv (ex: “Site-ul meu”, “Blog WordPress”)
- URL-ul webhook — adresa HTTPS la care Contento va trimite articolele prin POST
- Token de acces (opțional) — un secret partajat pentru autentificarea cererilor
Poți configura webhook-ul din Setări → Integrări → Webhook în dashboard-ul Contento.
3. Autentificare
Dacă ai configurat un token de acces, Contento îl va trimite în header-ul Authorization al fiecărei cereri:
Authorization: Bearer <token-ul-tău>
Este recomandat să verifici acest token în endpoint-ul tău pentru a te asigura că cererea provine de la Contento. Exemplu în 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. Structura payload-ului
Când publici un articol, Contento trimite un request POST cu header-ul Content-Type: application/json și următoarea structură JSON:
{
"timestamp": "2026-02-13T15:45:30Z",
"data": {
"articles": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Titlul articolului",
"content_markdown": "# Titlul articolului\n\nConținutul în format Markdown...",
"content_html": "<h1>Titlul articolului</h1><p>Conținutul în format HTML...</p>",
"meta_description": "Descrierea meta SEO a articolului",
"created_at": "2026-02-10T10:20:30Z",
"slug": "titlul-articolului",
"image_url": "https://example.com/storage/imagine.jpg",
"tags": ["seo-tips", "marketing-digital"]
}
]
}
}
5. Câmpurile articolului
| Câmp | Tip | Descriere |
|---|---|---|
id |
string (UUID) | Identificatorul unic al articolului |
title |
string | Titlul articolului |
content_markdown |
string | Conținutul articolului în format Markdown |
content_html |
string | Conținutul articolului în format HTML |
meta_description |
string | Descrierea meta SEO a articolului |
created_at |
string (ISO 8601) | Data și ora creării articolului |
slug |
string | Slug-ul URL-friendly al articolului |
image_url |
string sau null | URL-ul imaginii principale (dacă există) |
tags |
array of strings | Tag-uri relevante extrase automat din sitemap (maxim 5, poate fi gol) |
6. Verificarea conexiunii
Înainte de a salva integrarea, poți verifica conexiunea folosind butonul “Verifică conexiunea” din pagina de configurare. Contento va trimite un request de test cu următoarea structură:
{
"timestamp": "2026-02-13T15:45:30Z",
"data": {
"verify": true
}
}
Endpoint-ul tău trebuie să răspundă cu un status HTTP 2xx pentru ca verificarea să fie considerată reușită.
7. Implementarea endpoint-ului
Endpoint-ul tău trebuie să:
- Accepte cereri POST cu
Content-Type: application/json - Verifice token-ul de acces din header-ul
Authorization(dacă este configurat) - Returneze un status HTTP
2xx(ex: 200, 201) pentru a confirma primirea - Răspundă în maximum 30 de secunde (altfel cererea expiră)
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) => {
// Verifică token-ul de acces
const authHeader = req.headers['authorization'];
if (WEBHOOK_TOKEN && (!authHeader || authHeader !== `Bearer ${WEBHOOK_TOKEN}`)) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Verificare conexiune
if (req.body.data?.verify) {
return res.status(200).json({ status: 'ok' });
}
// Procesează articolele
const { articles } = req.body.data;
articles.forEach(article => {
console.log(`Articol primit: ${article.title}`);
console.log(`Slug: ${article.slug}`);
console.log(`HTML: ${article.content_html.substring(0, 100)}...`);
// Salvează articolul în baza de date, publică-l pe site etc.
});
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():
# Verifică token-ul de acces
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()
# Verificare conexiune
if payload.get('data', {}).get('verify'):
return jsonify({'status': 'ok'}), 200
# Procesează articolele
articles = payload['data']['articles']
for article in articles:
print(f"Articol primit: {article['title']}")
# Salvează articolul în baza de date, publică-l pe site etc.
return jsonify({'status': 'received'}), 200
<?php
$webhookToken = getenv('CONTENTO_WEBHOOK_TOKEN');
// Verifică token-ul de acces
$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);
// Verificare conexiune
if (!empty($payload['data']['verify'])) {
http_response_code(200);
echo json_encode(['status' => 'ok']);
exit;
}
// Procesează articolele
foreach ($payload['data']['articles'] as $article) {
error_log("Articol primit: " . $article['title']);
// Salvează articolul în baza de date, publică-l pe site etc.
}
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
# Verifică token-ul de acces
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)
# Verificare conexiune
if payload.dig('data', 'verify')
return { status: 'ok' }.to_json
end
# Procesează articolele
payload['data']['articles'].each do |article|
puts "Articol primit: #{article['title']}"
puts "Slug: #{article['slug']}"
# Salvează articolul în baza de date, publică-l pe site etc.
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")
// Verifică token-ul de acces
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)
// Verificare conexiune
if payload.Data.Verify {
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
return
}
// Procesează articolele
for _, article := range payload.Data.Articles {
fmt.Printf("Articol primit: %s\n", article.Title)
fmt.Printf("Slug: %s\n", article.Slug)
// Salvează articolul în baza de date, publică-l pe site etc.
}
json.NewEncoder(w).Encode(map[string]string{"status": "received"})
}
func main() {
http.HandleFunc("/contento/webhook", webhookHandler)
http.ListenAndServe(":8080", nil)
}
8. Probleme comune
Dacă webhook-ul nu funcționează corect, verifică următoarele:
- URL-ul este accesibil public — endpoint-ul trebuie să fie disponibil pe internet, nu doar local
- Folosești HTTPS — recomandăm utilizarea unui URL securizat cu certificat SSL valid
- Token-ul coincide — verifică dacă token-ul configurat în Contento este identic cu cel din server (fără spații sau caractere suplimentare)
- Endpoint-ul acceptă JSON — asigură-te că serverul procesează corect cereri cu
Content-Type: application/json - Timpul de răspuns — endpoint-ul trebuie să răspundă în maximum 30 de secunde
- Platforme de hosting — dacă folosești Vercel, Netlify sau altă platformă serverless, asigură-te că ruta webhook-ului este accesibilă public fără autentificare suplimentară
9. Best practices
- Verifică întotdeauna token-ul — validează header-ul Authorization pentru a te asigura că cererea provine de la Contento
- Loghează cererile primite — păstrează un log al webhook-urilor primite pentru debugging
- Răspunde rapid — procesează articolul asincron dacă operația durează mult (ex: folosind o coadă de mesaje)
- Monitorizează availability-ul — configurează alerte pentru cazul în care endpoint-ul devine indisponibil
- Error Handling — implementează un mecanism de error handling în endpoint pentru a preveni pierderea articolelor
10. Contact
Dacă ai întrebări sau ai nevoie de ajutor cu integrarea webhook, contactează-ne la [email protected].