← All articles

Release Notes

SnipVault 1.3.2: Snippet Functions — Webhook Endpoints from PHP Snippets

Turn a published PHP snippet into a signed inbound HTTP endpoint at /sv/{slug}. Secret-header auth, rate limits, invocation logs, admin replay, and new Snippet Engineer tools to author and fixture-test webhooks.

August 22, 2026 4 min read Mark Ashton

  • release
  • functions
  • webhooks
  • snippet engineer
  • security
Abstract cover artwork for SnipVault 1.3.2: Snippet Functions — Webhook Endpoints from PHP Snippets

SnipVault 1.3.2 shipped on 22 August 2026. The headline is Snippet Functions: a published PHP snippet can become a real inbound HTTP endpoint — a webhook, a tiny API, a Stripe or Slack receiver — without standing up a custom plugin or exposing admin-ajax.php.

The URL is short on purpose. https://yoursite.com/sv/stripe-webhook is the pretty permalink. The REST fallback is /wp-json/snipvault/v1/functions/stripe-webhook/invoke if you cannot use rewrites. Both hit the same isolated runner.

The longer product guide is Snippet Functions. This is the release note.

What a function actually is

You write a PHP snippet the way you already do. On the Function card you flip Enable as function, pick a slug, and choose how callers authenticate. SnipVault generates a secret (32 characters, rotate-only — a settings save cannot plant a weak one), signs the file with the same HMAC you already use, and starts accepting GET and/or POST.

Inside the snippet you do not read raw $_POST. The runner sets a request context on $snipvault_function:

  • method, headers (secrets redacted), json, body, query, ip, slug, snippet_id

Return an array with status, optional headers, and body (or json). Echoed JSON still works. A redirect key becomes a 302. Guard the handler so a normal page-load or sandbox probe is a no-op:

if (!isset($snipvault_function) || !is_array($snipvault_function)) { return; }

That guard is the difference between a webhook and a snippet that also runs on plugins_loaded. The Snippet Engineer webhook recipe writes it for you.

Auth that is not "hope the URL stays secret"

Four modes ship in this first cut:

  • Secret header (default) — X-SnipVault-Secret or Authorization: Bearer …, compared with hash_equals
  • Logged in — WordPress cookie, POST only so a logged-in browser cannot be CSRF'd by a GET to /sv/{slug}
  • Capability — same cookie rule, plus a required capability field on the Function card (not reused from the snippet's page-load cap)
  • Public — only if the security audit is low *and* you explicitly confirm. No silent public endpoints.

Unpublished snippets 404. Safe Mode and SNIPVAULT_DISABLE_PHP_SNIPPETS return 503. Payloads over 1 MB are 413. Risk above your deploy ceiling is 403. The file on disk must still match the signed stored content — a tamper is a denied invoke, not a surprise execution.

Rate limits default to 60 requests per minute per slug per IP (max 600). Unauthenticated noise is capped at 30/min so a guessed secret cannot become a free load test. Timeouts clamp between 1 and 30 seconds (default 15).

Logs, replay, and a dashboard that is not folklore

Each function keeps the last 50 invocations: method, status, duration, IP, auth result. Successful and test runs store a redacted, size-capped payload so you can replay them as the current admin. 401/403 bodies are not stored. An attacker cannot plant a payload and wait for someone to hit Replay. Agent tests and prior admin replays stay replayable.

The dashboard summary shows today's invocation count, error rate, enabled function count, and the slowest slug. That is enough to notice a webhook that started taking 800ms after a plugin update.

Rotate the secret from the Function card when a contractor leaves or a Slack channel saw it. The agent can expose and test a function; it never receives the live secret. Copy X-SnipVault-Secret from the card yourself.

The agent can author the webhook

1.3.2 adds two Snippet Engineer tools: expose_as_function and test_function. Ask for a Stripe, GitHub, Slack, or generic webhook. The agent writes a handler against $snipvault_function, prefixes helpers (sv_wh_), exposes the slug with secret-header auth, fixture-tests a JSON body (drafts allowed, no live secret required), then asks you to deploy.

GitHub import cannot arm a live endpoint. Sidecar metadata cannot turn a function on or plant a secret. New imports stay disabled until an admin enables them on that site. That is the same "repo is not production" rule we already wanted for GitHub sync.

What else is in 1.3.2

Functions were the feature. The rest of the release is the kind of operational debt that only shows up after 1.3.0 landed in the wild:

  • Relative snippet paths. Files are stored as snipvault/slug_123.php under uploads, not absolute filesystem paths. Host migrations no longer break PHP/CSS/JS. Existing absolute paths remap at runtime and rewrite on upgrade. The first cut of that change fatally required a CWD-relative path — 1.3.2 resolves to the current uploads directory (with an ID glob fallback).
  • GitHub tokens that last more than eight hours. Expiring OAuth access tokens refresh through the hosted broker before API calls. A 401 retries once with a new token. Access and refresh tokens moved out of snipvault_settings into snipvault_github_auth, so an unrelated settings save cannot wipe the connection. Existing tokens migrate automatically.
  • GitHub Library 404s when the configured base path did not exist yet now return an empty file list. Private-repo auth failures report as reconnect errors, not a missing REST route.
  • Rewrite warnings on every request for /sv/{slug}$matches[1] is a literal replacement at registration time.
  • Light-mode project cards use the same surface treatment as automation cards.

Who this is for

If you have been stuffing webhook PHP into a mu-plugin, a hidden REST route in a one-off plugin, or a "log the payload" snippet that never had auth, Functions is the container. Same file-based storage, same HMAC, same audit ceiling, now with a URL you can give Stripe.

If you do not need inbound HTTP, ignore the Function card. Regular snippets did not change.

1.3.2 is a free update for every existing license. Enable a function on a staging site first, send one signed POST, read the log, then point the vendor at production.

Next

Ready to upgrade your snippet workflow?

GitHub sync, the Snippet Engineer, and security auditing — in one WordPress plugin.