tabtablabs
  • Blog
← Back to blog
Oct 22, 2025

How to Connect Outrank.so Webhooks to n8n (Free Template)

Download an n8n workflow to auto-publish Outrank articles to GitHub with auth, versioning, and SEO front matter.

By James Le

Outrank.so + n8n

What you’ll build

When Outrank publishes content, it sends a POST webhook to n8n. This workflow:

  1. Verifies the request using a Bearer token (Authorization header).
  2. Splits the incoming batch of articles into individual items.
  3. Checks your GitHub repo for an existing file by slug.
  4. Creates or updates an .mdx file with front‑matter and article content.

The incoming event is publish_articles and contains an array of articles in the payload (example included below).


Prerequisites

  • n8n (self‑hosted or Cloud) with a public base URL (HTTPS).
  • Outrank account with the Webhook integration (you’ll add your n8n URL there). Use coupon code TABTABLABS at checkout.
  • GitHub repo where articles should live (e.g., a Next.js/Contentlayer or Astro content folder).
  • A GitHub Personal Access Token (classic) with at least repo scope, saved in n8n as a GitHub API credential.

1) Download the workflow (anonymized)

Use the placeholder download link (replace with your hosted file): Download the template

Click to view the n8n workflow JSON
{
  "name": "Outrank → n8n: Webhook (POST)",
  "nodes": [
    {
      "parameters": {
        "content": "## Set Up\n- Replace `your-secret-webhook-path` with a unique secret path.\n- Set `YOUR_OUTRANK_WEBHOOK_TOKEN` to the token configured in Outrank.\n- Swap `YOUR_GITHUB_USERNAME_OR_ORG` and the repo URL with your GitHub details.\n- Update `YOUR_AUTHOR_NAME_OR_BOT` to the author you want in front matter.\n\nKeep the Authorization expression on the left side as-is.",
        "height": 256,
        "width": 464
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        480,
        384
      ],
      "id": "beec96bb-18b1-4a08-8bd5-7c8e233be007",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "your-secret-webhook-path",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        640,
        688
      ],
      "id": "b7c43da8-c968-4ea6-82f3-f825d5de4154",
      "name": "Webhook",
      "webhookId": "b387ceda-aea3-4599-b9d0-9edba29f6756"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.headers.authorization && $json.headers.authorization.split(' ')[1] }}",
              "rightValue": "YOUR_OUTRANK_WEBHOOK_TOKEN",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        848,
        784
      ],
      "id": "c593b905-99aa-496a-be74-002bc56ad851",
      "name": "Check Auth"
    },
    {
      "parameters": {
        "fieldToSplitOut": "body.data.articles",
        "options": {
          "includeBinary": false
        }
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        1072,
        784
      ],
      "id": "15c264ff-3741-43e5-a5b8-9534b39ec04a",
      "name": "Split Out"
    },
    {
      "parameters": {
        "resource": "file",
        "operation": "list",
        "owner": {
          "__rl": true,
          "value": "YOUR_GITHUB_USERNAME_OR_ORG",
          "mode": "name"
        },
        "repository": {
          "__rl": true,
          "value": "https://github.com/YOUR_GITHUB_USERNAME_OR_ORG/YOUR_REPO",
          "mode": "url"
        },
        "filePath": "=content/outrank/"
      },
      "type": "n8n-nodes-base.github",
      "typeVersion": 1.1,
      "position": [
        1072,
        592
      ],
      "id": "9f1c12cd-0adf-4c73-b101-2c441941e698",
      "name": "List files",
      "webhookId": "63a6c787-396c-4238-bfdd-961bd0c90381"
    },
    {
      "parameters": {
        "mode": "expression",
        "numberOutputs": 2,
        "output": "={{ $('List files').all().some(f => f.json.name === `${$json.slug}.mdx`) }}",
        "looseTypeValidation": true
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.3,
      "position": [
        1296,
        688
      ],
      "id": "d48aaa9d-7fcc-4d6e-ba2b-79b7aa24b5a0",
      "name": "Switch"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "leftValue": "={{ $json.content_markdown !== undefined }}",
              "rightValue": "=",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1520,
        592
      ],
      "id": "3e1c517f-de81-485f-bad0-b6136e6c43c2",
      "name": "If"
    },
    {
      "parameters": {
        "resource": "file",
        "owner": {
          "__rl": true,
          "value": "YOUR_GITHUB_USERNAME_OR_ORG",
          "mode": "name"
        },
        "repository": {
          "__rl": true,
          "value": "https://github.com/YOUR_GITHUB_USERNAME_OR_ORG/YOUR_REPO",
          "mode": "url"
        },
        "filePath": "=content/outrank/{{ $json.slug }}.mdx",
        "fileContent": "=---\ntitle: \"{{ $json.title }}\"\ndescription: \"{{ $json.meta_description }}\"\nauthor: YOUR_AUTHOR_NAME_OR_BOT\ndate: {{ $json.created_at.slice(0, 10) }}\ntags: {{ $json.tags }}\nimage_url: {{ $json.image_url }}\n---\n\n{{ $json.content_markdown }}",
        "commitMessage": "=add: {{ $json.id }}"
      },
      "type": "n8n-nodes-base.github",
      "typeVersion": 1.1,
      "position": [
        1744,
        592
      ],
      "id": "e25dc23c-3f21-4c80-95da-fb8691d986df",
      "name": "Create a file",
      "webhookId": "680c657e-c528-486e-8041-963388b0e879"
    },
    {
      "parameters": {
        "resource": "file",
        "operation": "edit",
        "owner": {
          "__rl": true,
          "value": "YOUR_GITHUB_USERNAME_OR_ORG",
          "mode": "name"
        },
        "repository": {
          "__rl": true,
          "value": "https://github.com/YOUR_GITHUB_USERNAME_OR_ORG/YOUR_REPO",
          "mode": "url"
        },
        "filePath": "=content/outrank/{{ $json.slug }}.mdx",
        "fileContent": "=---\ntitle: \"{{ $json.title }}\"\ndescription: \"{{ $json.meta_description }}\"\nauthor: YOUR_AUTHOR_NAME_OR_BOT\ndate: {{ $json.created_at.slice(0, 10) }}\ntags: {{ $json.tags }}\nimage_url: {{ $json.image_url }}\n---\n\n{{ $json.content_markdown }}",
        "commitMessage": "=update: {{ $json.id }}"
      },
      "type": "n8n-nodes-base.github",
      "typeVersion": 1.1,
      "position": [
        1520,
        784
      ],
      "id": "bbb99226-3c87-4a32-b967-0f02a78781b1",
      "name": "Edit a file",
      "webhookId": "57fed4d5-9627-47bd-ab08-f06f4c0a5b46"
    }
  ],
  "pinData": {},
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Check Auth",
            "type": "main",
            "index": 0
          },
          {
            "node": "List files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Auth": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List files": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Edit a file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Create a file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "cefb2e37-65c4-4e2e-8807-76d4e3610188",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "b61787c246f3413fc34965454400ce636f3bc2bcc8d1d5df9c3bcc72ab255ad7"
  },
  "id": "lCPwJCAqHWZQGqwG",
  "tags": [
    {
      "updatedAt": "2025-10-25T20:07:37.769Z",
      "createdAt": "2025-10-25T20:07:37.769Z",
      "id": "3bsYGGQ3rKxGh4qM",
      "name": "template"
    }
  ]
}

2) Import the workflow into n8n

  1. In n8n, go to Workflows → Import from File.
  2. Select the anonymized JSON you downloaded above.
  3. Open the imported workflow to configure placeholders.

3) Replace placeholders with your values

A) Webhook node

  • HTTP Method: POST
  • Path: your-secret-webhook-path → change to a random secret string (e.g., outrank-9b1de2f0).
  • Public URL: After saving or activating, n8n will expose: https://<your-n8n-domain>/webhook/<your-secret-webhook-path>

B) Check Auth (IF) node

  • This checks the header Authorization: Bearer <token> from Outrank and compares it with a value you control.

  • Replace YOUR_OUTRANK_WEBHOOK_TOKEN with the token you generate and configure on the Outrank side.

    • Keep the left expression as‑is: ={{ $json.headers.authorization && $json.headers.authorization.split(' ')[1] }}
  • Outcome: only requests with the correct Bearer token proceed.

C) GitHub nodes (List / Create / Edit file)

  • In each GitHub node, set the GitHub API credential you saved in n8n.

  • Replace placeholders:

    • YOUR_GITHUB_USERNAME_OR_ORG
    • https://github.com/YOUR_GITHUB_USERNAME_OR_ORG/YOUR_REPO
  • File paths: The template writes to content/outrank/. Change this if your CMS expects a different folder (e.g., src/content/blog/).

D) Front‑matter & file content

  • Update author: YOUR_AUTHOR_NAME_OR_BOT to your preferred author.
  • The template writes {{ $json.content_markdown }} to the .mdx body and maps common fields (title, meta_description, created_at, image_url, tags).
  • You can add more fields or transform content via additional nodes (e.g., Function, Set).

4) Activate & connect Outrank

  1. Activate your n8n workflow.
  2. In Outrank → Integrations → Webhooks, add your n8n webhook URL: https://<your-n8n-domain>/webhook/<your-secret-webhook-path>
  3. Set the Access/Bearer token to the same secret you used in Check Auth.
  4. Save.

5) Test the end‑to‑end flow

Go to https://outrank.so/dashboard/integrations and click the "Play" button to send a test webhook. If you’re new to Outrank, sign up via https://outrank.so/?via=tabtablabs and use coupon code TABTABLABS.

Outrank.so Webhook Test

After the request, check your GitHub repo for /content/outrank/sample-article-title-for-testing.mdx. If the file already existed, the workflow edits it; otherwise it creates it.


6) Troubleshooting

  • 401 / Not Authorized in n8n Ensure the Authorization header is present and the Bearer token matches the value you configured in Check Auth.

  • GitHub node errors (403/404/permission)

    • Verify the GitHub credential in n8n (token scopes & repo access).
    • Ensure owner, repository URL, and filePath are correct.
  • No file created

    • Confirm that content_markdown exists in the payload (the template has an If check to guard against missing content).
    • Inspect the Execution log in n8n for node‑by‑node data.
  • Folder structure differences Adjust filePath and front‑matter to match your CMS (e.g., add draft, category, or canonical_url).


7) Security best practices (quick hits)

  • Use a long, random webhook path (e.g., outrank-<uuid>).
  • Validate the Authorization header (already included).
  • Optionally restrict by IP allowlist or add a secondary secret inside the body.
  • Keep your GitHub PAT scoped as narrowly as possible; rotate regularly.

8) Extending the template (ideas)

  • CMS adapters: Instead of GitHub, write to Notion, Sanity, Supabase, or a headless CMS API.
  • Image handling: Download images, optimize, and persist to your storage/CDN.
  • Link hygiene: Preflight URLs, fix relative links, add UTM params.
  • SEO: Auto‑generate slug when missing; enrich front‑matter with readingTime, og:image, etc.
  • Notifications: Post to Slack/Discord when a file is created or updated.

Appendix — Where to put your own info (quick checklist)

  • Webhook

    • Path: your-secret-webhook-path → change to your own secret.
  • Check Auth

    • Replace YOUR_OUTRANK_WEBHOOK_TOKEN with the token you configure in Outrank.
  • GitHub nodes

    • Set GitHub API credential in n8n.
    • Replace YOUR_GITHUB_USERNAME_OR_ORG and repo URL.
    • Tweak filePath folder (content/outrank/) to match your project.
    • Update author in the front‑matter.