Events · HTTP POST
מסירת מאמרים מיידית לשרת שלך — ללא polling, ללא מפתחות API.
מסירת מאמרים מיידית לשרת שלך — ללא polling, ללא מפתחות API.
הוסף את כתובת ה-HTTPS שלך למטה. VellumUp ישלח POST אליה בכל פעם שאירוע יתרחש.
כל משלוח חתום עם HMAC-SHA256. אמת את הכותרת X-VellumUp-Signature כדי לוודא שהגיע מאיתנו.
פרסר את ה-JSON, שמור את המאמר במסד הנתונים שלך, הפעל rebuild, או עשה כל מה שתרצה.
נקודת הקצה שלך מקבלת אחד מהאירועים הבאים בכל משלוח — קרא את הכותרת X-VellumUp-Event כדי לנתב בהתאם
article.publishedמופעל כאשר מאמר חדש נוצר, או כאשר סטטוס המאמר משתנה ל-published
article.unpublishedמופעל כאשר מאמר מועבר חזרה לסטטוס טיוטה
article.updatedמופעל כאשר כותרת, תוכן, מילות מפתח או תמונת נושא של מאמר נערכים ונשמרים
article.translatedמופעל בכל פעם שתרגום לשפה נשמר — כולל שדות language_code ו-language_name באובייקט ה-data
כל משלוח מכיל את המאמר המלא במצבו הנוכחי. כל האירועים חולקים את אותם השדות — רק article.translated מוסיף language_code ו-language_name.
slug לבניית כתובת המאמר באתר שלך. הוא בטוח ל-URL, ייחודי לכל דומיין, ויציב גם לאחר עדכונים.| שדה | סוג | תיאור |
|---|---|---|
| id | string (UUID) | מזהה ייחודי של המאמר בחשבונך |
| slug | string | מזהה ידידותי ל-URL — השתמש בו לבניית כתובת המאמר (לדוגמה yourblog.com/blog/slug) |
| title | string | כותרת המאמר המלאה |
| content | string | גוף המאמר המלא. מרקדאון כברירת מחדל — או HTML אם בחרת בפורמט HTML בעת יצירת נקודת הקצה |
| cover_image | string | null | כתובת תמונת הנושא שנוצרה על ידי AI, או null אם הדילוג על יצירת תמונה |
| cover_image_url | string | null | Same as cover_image (explicit naming for clarity) |
| meta_description | string | null | תיאור מטא לקידום אתרים, עד 160 תווים |
| focus_keyword | string | null | מילת מפתח ראשית שהמאמר מכוון אליה |
| secondary_keywords | string[] | מילות מפתח תומכות הכלולות בתוכן |
| key_takeaways | { takeaway, _heading? }[] | מערך של אובייקטי key takeaway. לכל אחד יש שדה takeaway. הפריט הראשון כולל גם _heading — כותרת הסעקשן מתורגמת לשפת המאמר (למשל Key Takeaways, נקודות מפתח). השתמש ב-_heading להצגת כותרת הbox בשפה הנכונה. |
| word_count | number | ספירת מילים משוערת של התוכן |
| reading_time_minutes | number | Estimated reading time in minutes (word_count ÷ 200) |
| website_url | string | null | כתובת URL מלאה של האתר המחובר |
| website_domain | string | דומיין האתר המחובר (לדוגמה yourblog.com) |
| status | "published" | "draft" | סטטוס המאמר הנוכחי |
| internal_link_slugs | string[] | Slugs of internal links the AI placed in this article — use these to show "Related Articles" on your site |
| og_title | string | Article title for social sharing & OG tags |
| og_description | string | Meta description (fallback to first 155 chars) |
| og_type | "article" | Content type for Open Graph metadata |
| created_at | string (ISO 8601) | מתי המאמר נוצר לראשונה |
| updated_at | string (ISO 8601) | מתי המאמר עודכן לאחרונה |
| language_code | string (BCP-47) | רק ב-article.translated — קוד BCP-47 לדוגמה "fr", "es", "he" |
| language_name | string | רק ב-article.translated — שם השפה קריא לאנשים, לדוגמה "French" |
דוגמת payload
{
"id": "del_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2026-04-22T10:30:00.000Z",
"data": {
"id": "f7e6d5c4-b3a2-1098-fedc-ba9876543210",
"slug": "how-to-improve-your-seo-in-2026",
"title": "How to Improve Your SEO in 2026",
"content": "# How to Improve Your SEO in 2026\n\nSearch engine optimization has evolved...",
"cover_image": "https://storage.supabase.co/article-images/user123/ai/hero_abc123.jpg",
"cover_image_url": "https://storage.supabase.co/article-images/user123/ai/hero_abc123.jpg",
"meta_description": "Learn proven SEO strategies for 2026. From technical optimization to content strategy.",
"focus_keyword": "improve SEO 2026",
"secondary_keywords": ["on-page SEO", "SEO tips", "search ranking", "technical SEO"],
"key_takeaways": [
{ "takeaway": "Technical SEO fixes (Core Web Vitals, indexability) deliver the fastest ranking gains.", "_heading": "Key Takeaways" },
{ "takeaway": "Content that matches search intent outperforms keyword-stuffed pages every time." },
{ "takeaway": "Building authoritative backlinks remains the most powerful off-page SEO signal." }
],
"word_count": 1840,
"reading_time_minutes": 9,
"internal_link_slugs": ["technical-seo-checklist", "on-page-seo-guide", "keyword-research-tools"],
"website_url": "https://yourblog.com",
"website_domain": "yourblog.com",
"status": "published",
"og_title": "How to Improve Your SEO in 2026",
"og_description": "Learn proven SEO strategies for 2026. From technical optimization to content strategy.",
"og_type": "article",
"created_at": "2026-04-22T10:30:00.000Z",
"updated_at": "2026-04-22T10:30:00.000Z"
}
}איך החתימה עובדת
כל משלוח כולל את הכותרת X-VellumUp-Signature בפורמט t=TIMESTAMP,v1=SHA256_HEX. לאימות: שרשר את ה-timestamp וגוף הבקשה הגולמי כ-"TIMESTAMP.RAW_BODY", חשב HMAC-SHA256 עם סוד החתימה שלך, והשווה עם v1 באמצעות פונקציה בזמן קבוע. תמיד השתמש בגוף הגולמי — לא JSON מפורסר.
אימות חתימה — דוגמאות קוד
const crypto = require('crypto');
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // whsec_...
function verifySignature(rawBody, sigHeader, secret) {
if (!sigHeader) return false;
// Parse "t=<timestamp>,v1=<hmac>"
const parts = Object.fromEntries(sigHeader.split(',').map(p => p.split('=')));
const { t: ts, v1: received } = parts;
if (!ts || !received) return false;
// Reject requests older than 5 minutes
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}מטפלים מלאים — אמת את החתימה, הגב 200 מיידית, ואז עבד את נתוני המאמר ברקע.
// app/api/webhook/route.ts
import { NextRequest } from 'next/server';
import { createHmac, timingSafeEqual } from 'crypto';
const SECRET = process.env.WEBHOOK_SECRET!;
function verify(rawBody: string, sig: string): boolean {
const parts = Object.fromEntries(sig.split(',').map(p => p.split('=')));
const { t: ts, v1 } = parts;
if (!ts || !v1) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = createHmac('sha256', SECRET)
.update(`${ts}.${rawBody}`)
.digest('hex');
try {
return timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
} catch { return false; }
}
export async function POST(req: NextRequest) {
const rawBody = await req.text();
const sig = req.headers.get('x-vellumup-signature') ?? '';
const event = req.headers.get('x-vellumup-event') ?? '';
if (!verify(rawBody, sig)) {
return new Response('Unauthorized', { status: 401 });
}
const { data } = JSON.parse(rawBody);
if (event === 'article.published') {
// Save to your database
// await db.articles.upsert({ where: { slug: data.slug }, ... })
console.log('New article:', data.title, '→', data.slug);
}
if (event === 'article.translated') {
// Save translation
console.log(`[${data.language_code}] ${data.title}`);
}
if (event === 'article.updated') {
// Update existing article
console.log('Updated:', data.title);
}
return new Response('OK', { status: 200 });
}סנכרון למסד נתונים או CMS
שמור מאמרים נכנסים ישירות למסד הנתונים או ל-CMS ללא ראש שלך ברגע שהם נוצרים — ללא ייצוא ידני.
// Upsert article into your database on publish
if (event === 'article.published') {
await db.articles.upsert({
where: { slug: data.slug },
update: { title: data.title, content: data.content, updatedAt: new Date() },
create: { slug: data.slug, title: data.title, content: data.content },
});
}הפעלת בנייה מחדש של האתר
קרא ל-deploy hook של ספק האירוח שלך (Vercel, Netlify וכו') לבניית האתר הסטטי שלך מחדש בכל פעם שמאמר חדש מגיע.
// Trigger a Vercel rebuild after publish
if (event === 'article.published') {
await fetch(process.env.VERCEL_DEPLOY_HOOK_URL, { method: 'POST' });
}
// Or for Netlify:
if (event === 'article.published') {
await fetch(process.env.NETLIFY_BUILD_HOOK, { method: 'POST' });
}סנכרון תוכן רב-לשוני
האזן לאירועי article.translated כדי לסנכרן אוטומטית גרסאות מתורגמות לדפי ה-locale המתאימים באתר שלך.
// Save translations to the correct locale path
if (event === 'article.translated') {
await db.translations.upsert({
where: { slug_locale: { slug: data.slug, locale: data.language_code } },
update: { title: data.title, content: data.content },
create: { slug: data.slug, locale: data.language_code,
title: data.title, content: data.content },
});
}שליחת התראות
פרסם ב-Slack, שלח אימייל, או דחף התראה לצוות שלך בכל פעם שמאמר חדש מתפרסם או מתעדכן.
// Post a Slack notification when a new article is published
if (event === 'article.published') {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `📝 New article published: *${data.title}*
${data.website_url}/${data.slug}`,
}),
});
}// 1. Install react-markdown
// npm install react-markdown
// 2. components/Article.tsx
import ReactMarkdown from 'react-markdown';
interface Article {
title: string;
content: string; // raw Markdown from webhook
slug: string;
cover_image?: string | null;
}
export function Article({ article }: { article: Article }) {
return (
<article>
{article.cover_image && (
<img src={article.cover_image} alt={article.title} />
)}
<h1>{article.title}</h1>
<div className="prose">
<ReactMarkdown>{article.content}</ReactMarkdown>
</div>
</article>
);
}
// 3. With syntax highlighting (optional)
// npm install react-markdown remark-gfm rehype-highlight
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight]}
>
{article.content}
</ReactMarkdown>אימות החתימה נכשל
ודא שאתה משתמש בגוף הבקשה הגולמי — לפני כל פרסור JSON. frameworks רבים מפרסרים את הגוף אוטומטית; הגדר אותם להעביר בתים גולמיים למסלול ה-webhook שלך.
קבלת משלוחים כפולים
VellumUp עשוי לנסות שוב משלוחים שנכשלו. הפוך את המטפל שלך לאידמפוטנטי על ידי בדיקת ה-slug לפני הכנסה, או השתמש באילוץ ייחודי על עמודת ה-slug.
נקודת הקצה מסיימת בזמן קצוב (המשלוח מסומן כנכשל)
VellumUp מחכה עד 8 שניות לתגובה. הגב עם HTTP 200 מיידית, ואז עבד את ה-payload באופן אסינכרוני בעבודת רקע או תור.
שדות null ב-payload
שדות כמו cover_image, meta_description ו-focus_keyword יכולים להיות null. תמיד טפל בערכי null במפורש בקוד שלך במקום להניח שהם קיימים.
משלוח הבדיקה מצליח אך משלוחים אמיתיים נכשלים
אירועי בדיקה משתמשים ב-payload פשוט יותר. ודא שהמטפל שלך מטפל בכל סוגי האירועים (article.published, article.updated, article.translated) ולא קורס על שדות בלתי צפויים.
תמיד אמת את החתימה
אל תעבד payload ללא אימות חתימת HMAC-SHA256. זה מבטיח שהבקשה הגיעה מ-VellumUp ולא שונתה.
דחה timestamps ישנים
השלך כל בקשה שבה |now − timestamp| > 300 שניות. זה מונע התקפות replay שבהן תוקף שולח מחדש בקשה תקינה שנלכדה.
השתמש בגוף הבקשה הגולמי
חתום על הבתים הגולמיים כפי שהתקבלו — לפני כל פרסור JSON. פרסור וסריאליזציה מחדש עלולים לשנות רווחים ולשבור את בדיקת החתימה.
הגב מהר, עבד בצורה אסינכרונית
החזר HTTP 200 תוך מספר שניות, אחרת VellumUp יסמן את המשלוח כנכשל. העבר עיבוד כבד לתור ברקע.