Setup i wdrożenie

Jak działa workflow Vistrify

Ten przewodnik jest dla osób, które chcą sprawdzić dokładnie, jak działa workflow od setupu do publikacji.

Najpierw sprawdź to tutaj

  • Darmowy trial obejmuje 1 stronę, a każda płatna strona ma własną subskrypcję.
  • Workflow zakłada: strona -> słowa kluczowe -> szkice -> kalendarz -> publikacja.
  • Cron generuje szkice do 2 dni przed publikacją, a w dniu publikacji skupia się głównie na samym publish.

1. Szybki start (10 minut)

  1. Załóż konto i przejdź przez wybór planu dla pierwszej strony.
  2. Dodaj stronę w zakładce Sites i wybierz rynek docelowy (kraj + język).
  3. Wygeneruj słowa kluczowe, utwórz szkice, sprawdź treść.
  4. Podłącz publikację (WordPress lub API), a potem zaplanuj daty w Calendar.

2. Integracja z WordPress (najprostsza)

Użyj tej opcji, jeśli Twój blog działa na WordPress.

  1. W WordPress przejdź do Users -> Profile -> Application Passwords i utwórz nowe hasło aplikacji.
  2. W Vistrify: Settings -> Publish connections -> WordPress.
  3. Uzupełnij pola: wp_url (adres strony), wp_username (użytkownik WP), wp_app_password (hasło aplikacji).
  4. Kliknij Save WordPress, następnie opublikuj testowo jeden szkic z Drafts -> Publish to WordPress.

Jeśli publikacja się powiedzie, artykuł pojawi się od razu jako wpis w WordPress.

3. Integracja przez Custom API / Webhook

Użyj tej opcji, jeśli nie masz WordPress albo publikujesz do własnego CMS.

  1. Dodaj webhook_url. To jest główny endpoint publikacji, na który Vistrify wyśle POST z artykułem.
  2. Opcjonalnie dodaj api_key. Vistrify wyśle go jako nagłówek Authorization: Bearer <token>.
  3. Jeśli chcesz wspierać update/delete dla już opublikowanych wpisów, ustaw update_webhook_url i delete_webhook_url. Możesz użyć placeholderów {{postId}}, {postId} albo :id.
  4. Wybierz update_method i delete_method. Domyślnie update używa PUT, a delete używa DELETE.
  5. Kliknij Save API, potem Test connection. Test wysyła bezpieczny payload i niczego nie publikuje.

Co oznacza każde pole

  • webhook_url: wymagany endpoint publikacji.
  • api_key: opcjonalny bearer token do autoryzacji.
  • update_webhook_url: opcjonalny endpoint aktualizacji. Jeśli go nie podasz, Vistrify spróbuje użyć głównego webhook_url.
  • delete_webhook_url: opcjonalny endpoint usuwania. Jeśli go nie podasz, Vistrify spróbuje użyć głównego webhook_url albo znanego publicznego URL wpisu.
  • update_method / delete_method: metody HTTP używane przy update/delete. Jeśli Twoja platforma wspiera tylko POST, ustaw POST i rozpoznawaj action w payloadzie.

Minimalny webhook (tylko publikacja)

Najprostsza wersja potrzebuje tylko webhook_url. Endpoint przyjmuje POST, zapisuje artykuł do Twojego CMS lub bazy i zwraca dowolne 2xx.

Jeśli nie zależy Ci jeszcze na późniejszych aktualizacjach i usuwaniu, wystarczy nawet { ok: true }.

Payload publikacji wysyłany przez Vistrify

To jest domyślny JSON wysyłany przez Custom API / Webhook. Niektóre pola są zduplikowane pod różnymi nazwami specjalnie dla kompatybilności z różnymi CMS-ami.

POST /posts
Content-Type: application/json
Authorization: Bearer <api_key>   // only if api_key is set

	{
	  "action": "publish",
	  "id": null,
	  "postId": null,
	  "externalPostId": null,
	  "slug": "article-slug",
	  "postUrlHint": "https://example.com/blog/article-slug",
	  "title": "Article title",
	  "body": "<h1>Article title</h1><p>Rendered HTML content</p>",
	  "content": "<h1>Article title</h1><p>Rendered HTML content</p>",
	  "html": "<h1>Article title</h1><p>Rendered HTML content</p>",
	  "content_html": "<h1>Article title</h1><p>Rendered HTML content</p>",
	  "markdown": "# Markdown content",
	  "content_markdown": "# Markdown content",
	  "body_markdown": "# Markdown content",
	  "articleTitle": "Article title",
	  "articleContent": "<h1>Article title</h1><p>Rendered HTML content</p>",
	  "excerpt": "Short meta description",
	  "metaDescription": "Short meta description",
	  "seoDescription": "Short meta description",
  "imageUrl": "https://cdn.vistrify.com/covers/generated/draft-123/article-title-v3.png",
	  "image_url": "https://cdn.vistrify.com/covers/generated/draft-123/article-title-v3.png",
	  "coverImageUrl": "https://cdn.vistrify.com/covers/generated/draft-123/article-title-v3.png",
	  "cover_image_url": "https://cdn.vistrify.com/covers/generated/draft-123/article-title-v3.png",
	  "featuredImage": "https://cdn.vistrify.com/covers/generated/draft-123/article-title-v3.png",
	  "featured_image": "https://cdn.vistrify.com/covers/generated/draft-123/article-title-v3.png",
	  "format": "html",
	  "sourceFormat": "markdown"
	}

Odpowiedź, której Vistrify oczekuje po publikacji

Do samej publikacji wystarczy dowolny status 2xx. Ale jeśli chcesz, żeby później działał update/delete, zwróć stabilny identyfikator wpisu i najlepiej publiczny URL.

  • Akceptowane klucze ID: id, post_id, postId, article_id, articleId, resource_id.
  • Akceptowane klucze URL: url, link, post_url, postUrl, permalink, article_url, articleUrl.
HTTP/1.1 201 Created
Content-Type: application/json

{
  "ok": true,
  "id": "post_123",
  "url": "https://example.com/blog/article-slug"
}

Pełny webhook CRUD (publikacja + update + delete)

To jest lepsza konfiguracja produkcyjna. Zwracaj ID/URL po publish i ustaw osobne endpointy update/delete albo użyj jednego endpointu z różnymi akcjami.

Jeśli używasz endpointów z placeholderem, Vistrify obsługuje {{postId}}, {postId} i :id. Jeśli placeholdera nie ma, a Vistrify zna ID wpisu, automatycznie dopnie /postId do końca URL.

Contract dla update

Vistrify wysyła ten sam payload co przy publikacji, ale z action = update oraz z ID/URL już opublikowanego wpisu.

Domyślnie update używa PUT. Jeśli endpoint zwraca błąd albo Twoja platforma woli POST, Vistrify potrafi ponowić próbę jako POST.

PUT /posts/{{postId}}
Content-Type: application/json
Authorization: Bearer <api_key>   // only if api_key is set

{
  "...publishFields": "same fields as publish payload",
  "action": "update",
  "id": "post_123",
  "postId": "post_123",
  "externalPostId": "post_123",
  "url": "https://example.com/blog/article-slug",
  "postUrl": "https://example.com/blog/article-slug",
  "externalPostUrl": "https://example.com/blog/article-slug"
}

Contract dla delete

Domyślnie Vistrify wywołuje delete endpoint metodą DELETE. Jeśli ustawisz delete_method = POST, payload delete jest wysyłany jako JSON.

Jeśli endpoint DELETE odpowie 405 albo 501, Vistrify automatycznie spróbuje jeszcze raz metodą POST z action = delete.

DELETE /posts/{{postId}}
Authorization: Bearer <api_key>   // only if api_key is set

// If delete_method is POST instead of DELETE, Vistrify sends JSON like:
{
  "action": "delete",
  "id": "post_123",
  "postId": "post_123",
  "externalPostId": "post_123",
  "slug": "article-slug",
  "url": "https://example.com/blog/article-slug",
  "postUrl": "https://example.com/blog/article-slug",
  "externalPostUrl": "https://example.com/blog/article-slug",
  "title": "Article title",
  "articleTitle": "Article title",
  "body": "# Markdown content",
  "content": "# Markdown content",
  "markdown": "# Markdown content",
  "articleContent": "# Markdown content",
  "format": "markdown"
}

Jak działa Test connection

Przycisk Test connection zawsze wysyła bezpieczny request do webhook_url. Nie publikuje treści i możesz potraktować go jak prosty health check z auth.

POST /posts
Content-Type: application/json
X-Vistrify-Test: 1
Authorization: Bearer <api_key>   // only if api_key is set

{
  "action": "test_connection"
}

Przykład działającego endpointu Node / Express

Ten przykład pokazuje pełny wariant publish + update + delete. Zastąp Mapę własnym CMS-em, bazą danych albo API.

import express from "express";
import crypto from "node:crypto";

const app = express();
app.use(express.json());

const posts = new Map();

function publicUrl(slug) {
  return `https://your-site.com/blog/${slug}`;
}

app.post("/posts", (req, res) => {
  if (req.header("X-Vistrify-Test") === "1" || req.body?.action === "test_connection") {
    return res.json({ ok: true });
  }

  const id = crypto.randomUUID();
  const slug = req.body.slug || id;

	  posts.set(id, {
	    id,
	    slug,
	    title: req.body.title,
	    body: req.body.content_html || req.body.html || req.body.body || "",
	    excerpt: req.body.metaDescription || req.body.excerpt || "",
	    imageUrl: req.body.imageUrl || req.body.coverImageUrl || "",
	  });

  return res.status(201).json({
    ok: true,
    id,
    url: publicUrl(slug),
  });
});

app.put("/posts/:id", (req, res) => {
  const existing = posts.get(req.params.id);
  if (!existing) return res.status(404).json({ ok: false, error: "Not found" });

  const slug = req.body.slug || existing.slug;
	  const nextPost = {
	    ...existing,
	    slug,
	    title: req.body.title,
	    body: req.body.content_html || req.body.html || req.body.body || existing.body,
	    excerpt: req.body.metaDescription || req.body.excerpt || existing.excerpt,
	    imageUrl: req.body.imageUrl || req.body.coverImageUrl || existing.imageUrl,
	  };

  posts.set(req.params.id, nextPost);

  return res.json({
    ok: true,
    id: req.params.id,
    url: publicUrl(slug),
  });
});

app.delete("/posts/:id", (req, res) => {
  if (!posts.has(req.params.id)) {
    return res.status(404).json({ ok: false, error: "Not found" });
  }

  posts.delete(req.params.id);
  return res.json({ ok: true, deleted: true, id: req.params.id });
});

app.listen(3000, () => {
  console.log("Webhook listening on http://localhost:3000");
});

Przykład jednego endpointu POST-only

Użyj tego wariantu, jeśli Twój CMS albo backend obsługuje tylko POST. W Settings ustaw webhook_url, update_webhook_url i delete_webhook_url na ten sam adres, a update_method oraz delete_method ustaw na POST.

W tym modelu rozgałęziasz logikę po polu action: publish, update, delete albo test_connection.

import express from "express";
import crypto from "node:crypto";

const app = express();
app.use(express.json());

const posts = new Map();

function publicUrl(slug) {
  return `https://your-site.com/blog/${slug}`;
}

app.post("/posts", (req, res) => {
  const action = req.body?.action;

  if (req.header("X-Vistrify-Test") === "1" || action === "test_connection") {
    return res.json({ ok: true });
  }

  if (action === "publish") {
    const id = crypto.randomUUID();
    const slug = req.body.slug || id;

	    posts.set(id, {
	      id,
	      slug,
	      title: req.body.title,
	      body: req.body.content_html || req.body.html || req.body.body || "",
	      excerpt: req.body.metaDescription || req.body.excerpt || "",
	      imageUrl: req.body.imageUrl || req.body.coverImageUrl || "",
	    });

    return res.status(201).json({
      ok: true,
      id,
      url: publicUrl(slug),
    });
  }

  if (action === "update") {
    const id = req.body.postId || req.body.id || req.body.externalPostId;
    const existing = id ? posts.get(id) : null;
    if (!id || !existing) {
      return res.status(404).json({ ok: false, error: "Not found" });
    }

    const slug = req.body.slug || existing.slug;
	    const nextPost = {
	      ...existing,
	      slug,
	      title: req.body.title,
	      body: req.body.content_html || req.body.html || req.body.body || existing.body,
	      excerpt: req.body.metaDescription || req.body.excerpt || existing.excerpt,
	      imageUrl: req.body.imageUrl || req.body.coverImageUrl || existing.imageUrl,
	    };

    posts.set(id, nextPost);

    return res.json({
      ok: true,
      id,
      url: publicUrl(slug),
    });
  }

  if (action === "delete") {
    const id = req.body.postId || req.body.id || req.body.externalPostId;
    if (!id || !posts.has(id)) {
      return res.status(404).json({ ok: false, error: "Not found" });
    }

    posts.delete(id);
    return res.json({ ok: true, deleted: true, id });
  }

  return res.status(400).json({ ok: false, error: "Unsupported action" });
});

app.listen(3000, () => {
  console.log("POST-only webhook listening on http://localhost:3000/posts");
});

Najważniejsze zasady

  • Jeśli chcesz update/delete, zwracaj stabilne id po publikacji.
  • Najlepiej zwracaj też publiczny URL wpisu, bo pomaga to w kolejnych operacjach i weryfikacji.
  • Jeśli Twój system wspiera tylko POST, ustaw update/delete na POST i rozpoznawaj pole action.
  • Endpoint musi naprawdę zapisywać, aktualizować albo usuwać wpis. Samo 200 OK bez zmian w CMS nie wystarczy.

4. Jak działa auto-publikacja

  1. W Calendar przeciągnij słowa kluczowe na konkretne dni albo użyj Auto-schedule.
  2. Dzienny cron generuje szkice do 2 dni wcześniej, a w dniu publikacji wykorzystuje gotowy draft do samej publikacji.
  3. Jeśli publikacja się nie powiedzie, szkic zostaje wygenerowany i czeka na ręczne kliknięcie Publish.

5. Najczęstsze błędy i szybkie rozwiązania

  • 401 / 403: niepoprawny login, hasło aplikacji lub token API.
  • 404: zły adres webhooka lub endpoint nie istnieje.
  • 502: Vistrify nie może połączyć się z Twoim endpointem albo endpoint zwrócił błąd.
  • "No connection configured": połączenie publikacji nie zostało zapisane dla aktywnej strony.
  • Wpis nie pojawia się na stronie: endpoint przyjmuje dane, ale nie zapisuje ich do CMS/bazy.
  • Update/delete nie działa: endpoint publish nie zwraca stabilnego id/url albo endpoint update/delete ignoruje postId.
  • Test connection nie przechodzi: sprawdź, czy firewall/WAF nie blokuje POST lub nagłówka X-Vistrify-Test.

6. Wsparcie

Jeśli chcesz, abyśmy sprawdzili integrację krok po kroku, napisz na info@vistrify.com i podaj domenę oraz typ integracji (WordPress lub API).

Następny krok

Jeśli setup wygląda sensownie, uruchom jedną stronę

Najpierw sprawdź dokumentację, potem uruchom jeden workflow i oceń produkt po wykonaniu, nie po obietnicach.

14-dniowy darmowy trial. Bez karty. 1 strona. Ograniczony limit triala.