Skip to content

Webhook Plugins Configuration

jmrGrav edited this page Aug 22, 2026 · 2 revisions

Webhook Plugins Configuration

After a successful build_site call, the Hugo MCP server can automatically trigger up to four post-build plugins: Cloudflare cache purge, IndexNow submission, Google Indexing API notification, and (as of 2026-08-22) a BetterStack heartbeat ping. All four are opt-in — if the relevant config section is absent or empty, the plugin is silently skipped. Failures in any plugin are logged as warnings and do not affect the build response.

This page covers how to provision credentials for each plugin, configure them in /etc/mcp-hugo-server-go/config.yaml, and harden the setup for production.


Security Requirements

These rules apply to all three plugins. Read before proceeding.

  • Never commit secrets to git. api_token, key, and the Google SA JSON must exist only in the host config file or on the server filesystem.
  • Lock down the config file so only the service user can read it:
    chmod 600 /etc/mcp-hugo-server-go/config.yaml
  • Apply the same permission to the Google service account JSON:
    chmod 600 /etc/mcp-hugo-server-go/google-sa.json

Full Config Reference

# /etc/mcp-hugo-server-go/config.yaml

cloudflare:
  zone_id: YOUR_ZONE_ID          # CF dashboard › Overview › Zone ID (right sidebar)
  api_token: YOUR_TOKEN          # CF token with Zone / Cache Purge / Purge permission

indexnow:
  key: YOUR_KEY                  # 32–128 char hex key you generate
  key_location: https://www.example.com/YOUR_KEY.txt   # public URL to key verification file
  host: www.example.com          # your site hostname
  endpoint: https://api.indexnow.org/indexnow           # optional; this is the default

google_indexing:
  service_account_path: /etc/mcp-hugo-server-go/google-sa.json
  daily_quota_limit: 180         # Google allows 200/day; 180 gives a safety margin
  quota_state_path: /var/lib/mcp-hugo-server-go/google-index-quota.json

heartbeat_hooks:
  - https://uptime.betterstack.com/api/v1/heartbeat/YOUR_HEARTBEAT_ID

Omit any section you do not need. Each plugin checks for the presence of its own section independently.


Plugin 1: Cloudflare Cache Purge

Purges the full zone cache via Cloudflare API v4 after every successful build. Also fires on delete_page (single-URL purge).

Provision

  1. Log in to the Cloudflare dashboard → My ProfileAPI TokensCreate Token.
  2. Use the Cache Purge template, or create a custom token with:
    • Permission: Zone / Cache Purge / Purge
    • Zone resources: scoped to your site's zone
  3. Copy the generated token — it is shown only once.
  4. Find your Zone ID on the dashboard Overview page (right sidebar under the domain name).

Configure

cloudflare:
  zone_id: "abc123def456..."
  api_token: "your-cloudflare-api-token"

Plugin 2: IndexNow

Submits all content page URLs to IndexNow (api.indexnow.org) after every successful build. Taxonomy and search pages (/tags/, /categories/, /authors/, /search/) are automatically filtered out.

Provision

  1. Generate a random hex key (32–128 characters):
    openssl rand -hex 32
  2. Save the key as a plain-text file in your Hugo site's static/ directory, using the key value as the filename:
    echo "YOUR_KEY" > static/YOUR_KEY.txt
  3. Build and deploy your site so the file is publicly accessible at the URL you will put in key_location. Verify with:
    curl https://www.example.com/YOUR_KEY.txt
  4. The response body must be exactly the key string (no extra whitespace).

Configure

indexnow:
  key: "YOUR_KEY"
  key_location: "https://www.example.com/YOUR_KEY.txt"
  host: "www.example.com"
  # endpoint is optional; defaults to https://api.indexnow.org/indexnow

Plugin 3: Google Indexing API

Sends a URL_UPDATED notification to the Google Indexing API v3 for each content URL after a successful build, using service-account JWT authentication. Taxonomy and search pages are automatically filtered out.

Google's free tier allows 200 URL notifications per day. The server tracks daily usage in a state file and stops submitting once daily_quota_limit is reached.

Provision

  1. Verify site ownership in Google Search Console.

  2. In Google Cloud Console, create or select a project and enable the Web Search Indexing API:

    • Navigate to APIs & ServicesLibrary → search for "Web Search Indexing API" → Enable.
  3. Create a service account:

    • IAM & AdminService AccountsCreate Service Account.
    • Grant it no project-level IAM roles (access is granted via Search Console, not Cloud IAM).
    • After creation, go to the service account → KeysAdd KeyJSON.
    • Download the JSON key file.
  4. Copy the JSON key to the server:

    scp google-sa.json user@yourserver:/etc/mcp-hugo-server-go/google-sa.json
    chmod 600 /etc/mcp-hugo-server-go/google-sa.json
  5. Grant the service account access in Search Console:

    • Go to Search Console → SettingsUsers and permissionsAdd user.
    • Enter the service account email (e.g. my-sa@my-project.iam.gserviceaccount.com).
    • Set the role to Owner (required by the Indexing API).
  6. Create the quota state directory and ensure it is writable by the service user:

    mkdir -p /var/lib/mcp-hugo-server-go
    chown mcp-hugo-server-go:mcp-hugo-server-go /var/lib/mcp-hugo-server-go

Configure

google_indexing:
  service_account_path: "/etc/mcp-hugo-server-go/google-sa.json"
  daily_quota_limit: 180
  quota_state_path: "/var/lib/mcp-hugo-server-go/google-index-quota.json"

Plugin 4: BetterStack Heartbeat

Pings a BetterStack dead-man's-switch heartbeat monitor after every build_site call — unsuffixed URL on success, /fail appended on OnBuildFailed. Fires asynchronously in a detached goroutine with a 15s bounded timeout; it never blocks the build response, and a ping failure is only logged as a warning. run_post_build_hooks also accepts an optional status param ("success"/"fail") to report a build/deploy result by hand outside the build_site path — this is what deploy.yml's heartbeat_report job uses via DEPLOY_HEARTBEAT_URL, since a deploy pipeline's pass/fail isn't itself a build_site call.

Treat the heartbeat URL as a secret, not a public identifier. Anyone who learns it can POST arbitrary success/fail pings and mask a real incident or trigger a false one. Never commit the real value — host-local config.yaml only (root-owned, outside git) plus the deploy pipeline's secret store.

Provision

  1. Create a heartbeat monitor in BetterStack (Monitors → Heartbeats → New).
  2. Set the expected period/grace to match your deploy + build cadence.
  3. Copy the ping URL — shown on the monitor's detail page.

Configure

heartbeat_hooks:
  - "https://uptime.betterstack.com/api/v1/heartbeat/YOUR_HEARTBEAT_ID"

Multiple URLs are supported (pings all of them); most deployments only need one.


Systemd Sandboxing

If the service unit uses ReadWritePaths (recommended), ensure the quota state directory and site directories are listed:

[Service]
ReadWritePaths=/var/lib/mcp-hugo-server-go /home/jm/hugo-site/content /home/jm/hugo-site/public

Without this, the server cannot write the quota state file and will log permission errors on every build.

If the Google SA JSON is in a directory owned by a different group (e.g. hugo-mcp), add the service user to that group:

usermod -aG hugo-mcp mcp-hugo-server-go
systemctl restart mcp-hugo-server-go

Plugin Behavior Summary

Plugin Trigger Filtered URLs Failure effect
Cloudflare Cache Purge build_site (full zone), delete_page (single URL) None Warning logged; build succeeds
IndexNow build_site Taxonomy + search pages Warning logged; build succeeds
Google Indexing API build_site Taxonomy + search pages Warning logged; build succeeds
BetterStack Heartbeat build_site (success and OnBuildFailed), or manually via run_post_build_hooks(status=...) N/A Warning logged; build succeeds (async, never blocks)

All four plugins fire in sequence (heartbeat fires asynchronously). No plugin failure blocks the others or the build response.

Clone this wiki locally