Skip to content

People Aggregator Webhook — Setup

Created 2026-04-19
Tags peoplecrmwebhookinfrastructure

Sub-minute freshness for the people aggregator when Fluent CRM tags or custom fields change. Triggered by an outbound webhook from a Fluent CRM automation funnel; received on the agents VPS by scripts/people-webhook-daemon.py and runs build-people-index.py --force-remote.

As deployed, the receiver binds to 127.0.0.1:9090 only — it is not reachable from the public internet until the nginx exposure steps below are performed.

FileLocation
scripts/people-webhook-daemon.pyin this repo, synced to VPS at /srv/baseworks/knowledge-base/scripts/
scripts/people-webhook.servicein this repo; installed to /etc/systemd/system/ on VPS
Shared secret/home/patrick/.people-webhook.secret on VPS (chmod 600, not in git)
Log/home/patrick/scripts/people-webhook.log on VPS
Terminal window
# 1. Generate a shared secret and save it
ssh patrick@46.224.129.16
openssl rand -hex 32 > ~/.people-webhook.secret
chmod 600 ~/.people-webhook.secret
cat ~/.people-webhook.secret # copy — used in FluentCRM funnel config
# 2. Install the systemd unit
sudo cp /srv/baseworks/knowledge-base/scripts/people-webhook.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now people-webhook.service
sudo systemctl status people-webhook.service
# 3. Sanity check (from VPS)
curl -s http://127.0.0.1:9090/health
curl -i -X POST \
-H "Authorization: Bearer $(cat ~/.people-webhook.secret)" \
http://127.0.0.1:9090/webhook/people-index
# expect HTTP 202, body: {"status": "queued"}

Public exposure (when you are ready — NOT done by default)

Section titled “Public exposure (when you are ready — NOT done by default)”

The receiver is local-only until you do this step. Two parts: nginx reverse proxy and a firewall rule. Pick a hostname — agents.baseworks.com is the natural fit.

Create /etc/nginx/sites-available/agents.baseworks.com:

server {
listen 80;
server_name agents.baseworks.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name agents.baseworks.com;
ssl_certificate /etc/ssl/cloudflare/baseworks.com.pem;
ssl_certificate_key /etc/ssl/cloudflare/baseworks.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
# Only the webhook path is exposed.
location = /webhook/people-index {
proxy_pass http://127.0.0.1:9090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location = /health {
proxy_pass http://127.0.0.1:9090;
}
location / {
return 404;
}
}

Then:

Terminal window
sudo ln -s /etc/nginx/sites-available/agents.baseworks.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Add an A record agents → VPS IP, proxied (orange cloud). The VPS IP is in 00-inbox/claude-code-shared-context.md.

In FluentCRM admin (crm.baseworks.com/wp-admin), go to FluentCRM → Automations → New Automation:

  1. Trigger: Pick one or more — e.g. “Tag Applied to Contact” (for the Tokyo Studio Alumni tag) and/or “Custom Field Updated” for romaji_name.
  2. Action: “Outgoing Webhook” (plugin comes with FluentCRM). Configure:
    • URL: https://agents.baseworks.com/webhook/people-index
    • Method: POST
    • Headers: Authorization: Bearer <paste secret from step 1>
    • Body: empty (the receiver doesn’t inspect body content).
  3. Save and enable. Test by manually applying/removing a tag on a contact; confirm the receiver’s log shows a 202 response within a few seconds.
  • Shared-secret auth (Authorization Bearer header), constant-time compare.
  • 30s cool-down coalesces duplicate triggers so a burst of CRM updates yields one aggregator run.
  • No request body parsing — a malformed webhook can only cause a 401 (wrong secret) or 404 (wrong path).
  • Rotate the secret by writing a new value into /home/patrick/.people-webhook.secret and updating the FluentCRM funnel. systemctl reload is not required — the daemon re-reads the file on every request.
  • To disable quickly: sudo systemctl stop people-webhook.service and remove the nginx location block (or return 404 it).