Skip to content
Dashboard

WordPress Integration

Automatically push every article Brainpercent generates straight into your WordPress site — title, body, featured image, category, and Yoast SEO fields — using WordPress's built-in REST API and Application Passwords. No plugin required beyond standard WordPress 5.6+.

What You Need

  • WordPress 5.6 or later (Application Passwords were added in 5.6)
  • An administrator or editor account on your WordPress site
  • HTTPS enabled on your WordPress site (Application Passwords require it)
  • A Brainpercent webhook URL (generated in your Brainpercent project settings)
  • Optional: Yoast SEO plugin installed if you want SEO metadata fields populated

Step 1 — Create a WordPress Application Password

Application Passwords let external services authenticate to your WordPress REST API without using your main login credentials. Each password can be revoked independently.

1

Log in to your WordPress admin dashboard and navigate to Users → Profile.

2

Scroll to the Application Passwords section near the bottom of the page.

3

Enter a name like Brainpercent and click Add New Application Password.

4

WordPress displays the password once. Copy it immediately — it will not be shown again. It looks like: AbCd EfGh IjKl MnOp QrSt UvWx (spaces are part of the password).

5

Build the Basic Auth credential: Base64(your_wp_username:your_application_password). You can generate this in a terminal:

echo -n "your_wp_username:AbCd EfGh IjKl MnOp QrSt UvWx" | base64
# Result example: eW91cl93cF91c2VybmFtZTpBYkNkIEVmR2g=

Security note: Use a dedicated WordPress user (e.g., "brainpercent-bot") with the Editor role, not your personal administrator account. This limits blast radius if the credential is ever leaked.

Step 2 — Find Your WordPress Category IDs

The WordPress REST API uses numeric category IDs, not names. Run this request to list all categories on your site:

curl -s "https://your-site.com/wp-json/wp/v2/categories?per_page=100" | \
  python3 -c "import sys,json; [print(c['id'], c['name']) for c in json.load(sys.stdin)]"

Keep a mapping of Brainpercent topic categories to WordPress category IDs. For example:

Brainpercent CategoryWordPress Category ID
Marketing5
Technology8
Business12

Step 3 — Write the Webhook Handler

Create a small server-side handler (Node.js example below) that receives Brainpercent'sarticle.publishedwebhook event and forwards the article to WordPress. Host this handler on any platform (Vercel, Railway, Render, your own server).

import crypto from 'crypto';
import express from 'express';

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

const WP_SITE     = process.env.WP_SITE_URL;        // e.g. https://your-site.com
const WP_AUTH     = process.env.WP_BASIC_AUTH;       // Base64 user:app-password
const BP_SECRET   = process.env.BP_WEBHOOK_SECRET;   // from Brainpercent settings
const CATEGORY_MAP = {
  marketing:  5,
  technology: 8,
  business:   12,
};

// Verify the Brainpercent HMAC signature
function verifySignature(rawBody, signature) {
  const expected = crypto
    .createHmac('sha256', BP_SECRET)
    .update(rawBody)
    .digest('hex');
  return `sha256=${expected}` === signature;
}

app.post('/webhooks/brainpercent', express.raw({ type: 'application/json' }), async (req, res) => {
  const sig = req.headers['x-brainpercent-signature'];
  if (!verifySignature(req.body, sig)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body);
  if (event.event !== 'article.published') {
    return res.json({ skipped: true });
  }

  const article = event.data;
  const wpCategoryId = CATEGORY_MAP[article.category?.toLowerCase()] ?? 1;

  // Build the WordPress post payload
  const wpPost = {
    title:          article.title,
    content:        article.content_html,
    status:         'publish',
    categories:     [wpCategoryId],
    featured_media: 0,  // Set after uploading the image (see Step 4)
  };

  // Upload featured image if available
  if (article.featured_image_url) {
    const mediaId = await uploadImageToWordPress(article.featured_image_url, article.title);
    if (mediaId) wpPost.featured_media = mediaId;
  }

  const wpRes = await fetch(`${WP_SITE}/wp-json/wp/v2/posts`, {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${WP_AUTH}`,
      'Content-Type':  'application/json',
    },
    body: JSON.stringify(wpPost),
  });

  if (!wpRes.ok) {
    const err = await wpRes.text();
    console.error('WordPress error:', err);
    return res.status(502).json({ error: err });
  }

  const created = await wpRes.json();
  res.json({ wp_post_id: created.id, wp_link: created.link });
});

async function uploadImageToWordPress(imageUrl, altText) {
  // Fetch the image
  const imgRes = await fetch(imageUrl);
  if (!imgRes.ok) return null;
  const buffer = await imgRes.arrayBuffer();
  const contentType = imgRes.headers.get('content-type') ?? 'image/jpeg';
  const filename = imageUrl.split('/').pop()?.split('?')[0] ?? 'featured.jpg';

  const mediaRes = await fetch(`${WP_SITE}/wp-json/wp/v2/media`, {
    method: 'POST',
    headers: {
      'Authorization':       `Basic ${WP_AUTH}`,
      'Content-Type':        contentType,
      'Content-Disposition': `attachment; filename="${filename}"`,
    },
    body: buffer,
  });

  if (!mediaRes.ok) return null;
  const media = await mediaRes.json();
  return media.id;
}

app.listen(3000);

Step 4 (Optional) — Populate Yoast SEO Fields

If you have the Yoast SEO plugin installed, its meta fields are exposed on the /wp-json/wp/v2/posts endpoint via the yoast_head_json field (read) and via the meta field (write). Add these fields to your wpPost payload:

// Append to wpPost object before sending to WordPress
wpPost.meta = {
  _yoast_wpseo_title:          article.seo_title    ?? article.title,
  _yoast_wpseo_metadesc:       article.seo_description ?? article.excerpt,
  _yoast_wpseo_focuskw:        article.focus_keyword ?? '',
  _yoast_wpseo_opengraph_image: article.featured_image_url ?? '',
};

Note: Writing to Yoast meta fields via the REST API requires the Yoast REST API integration to be enabled. Consult Yoast's developer docs at developer.yoast.com for the exact field names — they can differ between Yoast Free and Yoast Premium.

Step 5 — Register the Webhook in Brainpercent

1

Go to Brainpercent Settings → Webhooks and click Add Webhook.

2

Set the URL to your handler endpoint, e.g. https://your-handler.com/webhooks/brainpercent.

3

Select the event article.published.

4

Copy the generated webhook secret and store it as the BP_WEBHOOK_SECRET environment variable in your handler.

5

Click Test Webhook. A sample payload will be sent and you should see a 200 OK response in Brainpercent's delivery log.

Common Gotchas

  • Application Passwords unavailable? Check that your WordPress site is running over HTTPS. Application Passwords are disabled on HTTP sites.
  • 401 Unauthorized from WordPress REST API? Some security plugins (Wordfence, WP-Cerber) block REST API requests by IP. Whitelist your handler's IP or add an exception rule in the plugin settings.
  • Images not appearing as featured image? The media upload step requires the WordPress user to have Author or higher role. Subscribers cannot upload media.
  • Content rendered as raw HTML tags? WordPress Classic Editor stores HTML directly. Gutenberg (Block Editor) treats content as serialized blocks. Pass <!-- wp:html --><div>...</div><!-- /wp:html --> wrapper blocks if your site uses Gutenberg. Consult the WordPress REST API Reference for block serialization details.
  • Duplicate posts on webhook retries? Brainpercent retries webhooks on non-2xx responses. Make your handler idempotent by checking if a post with the same slug already exists before creating a new one.

For the full webhook payload schema and retry policy, see Webhooks & Async guide.