Skip to main content

Webhook Endpoint

A self-hosted PayPal webhook receiver runs on the opshell server and is exposed through the existing Cloudflare tunnel.

Status​

ItemState
Receiver serviceRunning (paypal-webhook), local health OK
Local bind127.0.0.1:5055 → container 5055
Public URLhttps://paypal-webhook.id86.net — returns 404 until the tunnel route is added
DNS CNAMECreated (paypal-webhook.id86.net → f379d8e1-….cfargotunnel.com, proxied)
Tunnel ingressPending — remote (dashboard) config; API token lacks Cloudflare Tunnel: Edit
PayPal webhook registrationPending public route + webhook ID
Signature verificationDisabled (REQUIRE_SIGNATURE=false) during the connectivity phase

Architecture​

Service​

FieldValue
Location/home/rezriz/github/automation/paypal-webhook/
RuntimePython 3.12 + Flask (Docker)
Containerpaypal-webhook
Networkexternal app-network (shared with cloudflared)
Internal port5055
Host bind127.0.0.1:5055 (loopback only)

Endpoints​

MethodPathPurpose
GET/healthzLiveness + configuration probe
GET/Minimal service info
POST/webhook/paypalPayPal webhook receiver

Data flow​

  1. PayPal POSTs the event with transmission headers.
  2. The receiver reads the raw JSON body.
  3. If REQUIRE_SIGNATURE=true, it calls PayPal verify-webhook-signature with the transmission headers and the event. Anything other than SUCCESS returns 401.
  4. Accepted and rejected events are appended to data/events/events-YYYY-MM-DD.jsonl, including the full payload.

Configuration​

Credentials are loaded from ~/.ssh/paypal/.env (see Sandbox Testing); app settings live in the service .env.

KeyPurpose
REQUIRE_SIGNATUREVerify deliveries with PayPal (true in production)
PAYPAL_WEBHOOK_IDWebhook ID returned by PayPal
PORT / DATA_DIRListen port / event log directory

Routing​

DNS is already created. The remote tunnel configuration must add:

- hostname: paypal-webhook.id86.net
service: http://paypal-webhook:5055

Local tunnel files (/opt/docker-data/tunnel/config/config.yml and config-v2.yml) were updated for reference, but the running tunnel uses the remote (dashboard) configuration. Add the rule in Zero Trust → Networks → Tunnels → VPS-GSM16 → Public Hostname. The current API token lacks Cloudflare Tunnel: Edit and returns 1001 Not authorized on the tunnel config endpoints.

Operations​

cd /home/rezriz/github/automation/paypal-webhook
docker compose build
docker compose up -d
docker compose logs -f paypal-webhook
curl -s http://127.0.0.1:5055/healthz | jq

Security​

  • Signature verification is mandatory once PAYPAL_WEBHOOK_ID is set.
  • The host bind is loopback-only; public access is exclusively through the tunnel.
  • .env and data/ are gitignored; credentials stay in ~/.ssh/paypal/.env.