Open App

Custom Webhook Integration

Connect Skribra to any platform or custom application using webhooks. Receive article data via HTTP POST requests whenever articles are published.

Custom webhooks allow you to integrate Skribra with any system that can receive HTTP requests, including custom CMS platforms, static site generators, headless CMS systems, or your own backend services.

Overview#

When you publish articles in Skribra, we send the content directly to your webhook endpoint as a JSON payload. You can then process this data however you need — store it in a database, publish to a custom CMS, trigger site rebuilds, or anything else your system requires.

The webhook integration supports both Markdown and HTML content formats, Bearer token authentication, and automatic retries for failed deliveries.

Setting Up Webhook Integration#

Navigate to Integrations → Custom Webhook in your Skribra dashboard. You'll need to provide the following information:

  • Integration Name: A unique name for your webhook integration (2-30 characters, no special characters).
  • Webhook Endpoint: The URL where you want to receive webhook events (must be a valid HTTPS URL).
  • Access Token: A secret key used to verify the authenticity of incoming webhook requests.

1. Webhook Endpoint

Enter the URL where you want to receive webhook events. This must be a valid HTTPS URL that accepts POST requests.

https://your-app.com/api/webhooks/skribra

2. Access Token

Set a secret access token that Skribra will send with every request in the Authorization header. This token is used to verify the authenticity of incoming webhook requests.

Authorization: Bearer your_secure_access_key_here

Important: Do not expose your access token publicly. Store it securely in environment variables and never hardcode it in your application or commit it to version control.

Verifying Webhook Requests#

Always verify that incoming webhook requests are authentic by checking the Bearer token in the Authorization header.

Verify Bearer Token
const express = require("express");
const app = express();

const ACCESS_KEY = process.env.SKRIBRA_ACCESS_KEY;

function verifyWebhook(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing authorization" });
  }
  const token = authHeader.split(" ")[1];
  if (token !== ACCESS_KEY) {
    return res.status(401).json({ error: "Invalid access key" });
  }
  next();
}

app.use(express.json());
app.post("/api/webhooks/skribra", verifyWebhook, (req, res) => {
  // Process the webhook payload
  res.status(200).json({ success: true });
});

Webhook Payload Structure#

When articles are published, Skribra sends a POST request with the following JSON payload:

{
  "event_type": "publish_articles",
  "timestamp": "2025-01-29T14:32:18.421Z",
  "data": {
    "articles": [
      {
        "id": "65c8a9d2e3b1f92a0f4a1c77",
        "title": "How to Build a High-Converting SaaS Landing Page",
        "slug": "how-to-build-high-converting-saas-landing-page",
        "content_markdown": "# Introduction\\n\\nLanding pages are...",
        "content_html": "<h1>Introduction</h1><p>Landing pages are...</p>",
        "meta_description": "Learn the key elements of high-converting SaaS landing pages...",
        "image_url": "https://images.skribra.com/articles/landing-page.webp",
        "created_at": "2025-01-29T00:00:00.000Z",
        "tags": ["saas", "landing-page", "conversion"]
      }
    ]
  }
}

Payload Fields

FieldTypeDescription
event_typestringEvent identifier (always "publish_articles")
timestampstringISO 8601 timestamp when the event occurred
data.articlesarrayArray of article objects
article.idstringUnique identifier for the article
article.titlestringThe title of the article
article.slugstringURL-friendly version of the title for permalinks
article.content_markdownstringThe article content in Markdown format
article.content_htmlstringThe article content in HTML format
article.meta_descriptionstringBrief description of the article for SEO purposes
article.image_urlstringURL of the main image associated with the article
article.created_atstringTimestamp when the article was created (ISO 8601)
article.tagsarrayArray of tags associated with the article
article.reading_timestringEstimated reading time (e.g., "5 min read")
article.related_articlesarrayArray of related article objects, each with 'url' and 'title' fields
article.ctastringCall-to-action HTML containing text and an <a> tag with class="cta-button"

About the Slug Field

The slug field is an SEO-optimized, URL-friendly string automatically generated from the article title. Use it as the pathname for the page displaying the article content (e.g., /blog/your-article-slug). If you're storing articles in a database, consider indexing this field for better query performance.

Response Requirements#

Your webhook endpoint should:

  • Return a 2xx status code (200-299) to indicate success
  • Respond within 30 seconds
  • Return any status code outside 2xx to indicate failure

If your endpoint returns an error or times out, Skribra will automatically retry the request.

Use Cases#

Custom webhooks are ideal for:

  • Static Site Generators: Trigger rebuilds when new content is published (Hugo, Jekyll, Gatsby, Astro, Next.js)
  • Headless CMS: Push content to Strapi, Sanity, Contentful, or other headless platforms
  • Custom Applications: Integrate with your own backend services or databases
  • Notification Systems: Send alerts to Slack, Discord, or email when articles are published
  • Content Syndication: Automatically distribute content to multiple platforms

Rendering Article Content#

Skribra provides article content in both Markdown and HTML formats via the content_markdown and content_html fields. Choose the format that best fits your application's needs.

Option 1: Using HTML Content (Recommended)

The content_html field contains pre-rendered HTML that's ready for display. This is the simplest approach — just insert the HTML and add your styles.

Render HTML Content
// React/Next.js component
interface ArticleProps {
  title: string;
  contentHtml: string;
  imageUrl?: string;
}

export function Article({ title, contentHtml, imageUrl }: ArticleProps) {
  return (
    <article className="prose">
      <h1>{title}</h1>
      {imageUrl && <img src={imageUrl} alt={title} />}
      <div dangerouslySetInnerHTML={{ __html: contentHtml }} />
    </article>
  );
}

Styling HTML Content

The HTML output uses standard semantic tags (h1, h2, p, ul, table, etc.). Add CSS to style these elements:

/* Article styles */
.prose {
  line-height: 1.75;
  color: #374151;
}
.prose h2 {
  font-size: 1.5rem;
  font-weight: 600;
  margin-top: 2rem;
  margin-bottom: 1rem;
}
.prose p {
  margin-bottom: 1.25rem;
}
.prose ul, .prose ol {
  padding-left: 1.5rem;
  margin-bottom: 1.25rem;
}
.prose table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.5rem 0;
}
.prose th, .prose td {
  border: 1px solid #e5e7eb;
  padding: 0.75rem;
}
.prose code {
  background: #f3f4f6;
  padding: 0.125rem 0.375rem;
  border-radius: 4px;
  font-size: 0.875em;
}

Option 2: Using Markdown Content

The content_markdown field gives you raw Markdown that you parse yourself. This provides more control over rendering and is ideal for custom styling or static site generators.

First, install a Markdown parser for your platform:

Install Markdown Parser
# Install markdown-it (recommended for server-side)
npm install markdown-it @types/markdown-it

# Or for client-side React components
npm install react-markdown remark-gfm

Then render the Markdown content:

Render Markdown
// lib/markdown.ts
import MarkdownIt from "markdown-it";

const md = new MarkdownIt({
  html: true,
  linkify: true,
  typographer: true,
});

export function renderMarkdown(markdown: string): string {
  if (!markdown) return "";
  return md.render(markdown);
}

// Usage in a page component
export default function ArticlePage({ article }) {
  return (
    <article className="prose">
      <h1>{article.title}</h1>
      <div
        dangerouslySetInnerHTML={{ 
          __html: renderMarkdown(article.content_markdown)
        }}
      />
    </article>
  );
}

Troubleshooting#

Webhook Not Receiving Requests

  • Verify your endpoint URL is correct and publicly accessible
  • Check that your server accepts POST requests at the specified path
  • Ensure your SSL certificate is valid (HTTPS is required)
  • Check your firewall rules allow incoming connections

Authentication Failures

  • Confirm you're checking the Authorization header correctly
  • Verify the access token in your environment matches the one set in Skribra
  • Check for trailing whitespace or newlines in your environment variables
  • Ensure the header format is exactly Bearer your_access_token (with a space)

Timeout Errors

  • Ensure your endpoint responds within 30 seconds
  • Process heavy operations asynchronously after sending the response
  • Consider using a queue system (Redis, RabbitMQ) for time-consuming tasks

Payload Parsing Errors

  • Verify your endpoint expects application/json content type
  • Check that you're parsing the request body as JSON
  • Validate all expected fields exist before accessing them

Tables Not Rendering

If tables appear as dashes and pipes instead of formatted tables, you're likely using a Markdown renderer that doesn't support GFM tables. Install and configure the remark-gfm plugin as shown in the Rendering section above.

Security Best Practices#

  • Always verify the webhook access token to ensure the request is genuine
  • Implement proper error handling and logging in your webhook receiver
  • Set up monitoring for your webhook endpoint to ensure it's always available
  • Consider implementing a retry mechanism for failed webhook deliveries
  • Store access tokens in environment variables, never in code
  • Use HTTPS for your webhook endpoint (HTTP is not supported)

Frequently Asked Questions#

Can I have multiple webhook endpoints?

Currently, each Skribra project can have one webhook endpoint. Contact support if you need to send to multiple destinations.

What happens if my endpoint is down?

Skribra automatically retries failed requests. If all attempts fail, you can manually retry from your dashboard.

Can I change the webhook URL after creation?

Yes, you can update your webhook configuration at any time in the Integrations settings.

Can I receive webhooks for article updates?

Currently, webhooks are sent when articles are published. Republishing an existing article will trigger a new webhook with the updated content.

Should I use Markdown or HTML format?

Use Markdown if you want more control over rendering and styling. Use HTML if you want pre-formatted content ready for display without additional processing.

For additional support, contact our team through the in-app chat or at [email protected].

Was this page helpful?

Skribra

AI-powered SEO content automation for blogs and websites.

© Copyright 2026. All rights reserved.