Public Actions — Improvement & Growth Plan
Package: capell-app/public-actions · Kind: package · Tier: premium · Product group: Capell Automation · Bundle: automation · Status: Draft
1. Snapshot
Section titled “1. Snapshot”Public Actions is a framework package that lets Capell sites accept untrusted public submissions, run a registered server-side handler against the validated payload, and fan the resulting submission out to configured outbound destinations (HTTP webhooks for Zapier, Pipedream, n8n, Make, or generic). It exposes four surfaces: a public web form (GET/POST /actions/{action}), an authenticated Zapier-style JSON API (/api/public-actions/zapier/*), five Filament admin resources (actions, destinations, submissions, dispatch attempts, integration tokens), and a queue job for durable dispatch. Key Actions are SubmitPublicActionAction (the orchestrator: resolve → spam-check → validate → idempotency → persist → handler → dispatch), DispatchPublicActionDestinationAction, and the integration-token lifecycle actions; persistence is five tables (public_actions, public_action_destinations, public_action_submissions, public_action_dispatch_attempts, public_action_integration_tokens). Runtime deps are capell-app/{admin,core,frontend} plus lorisleiva/laravel-actions; it supports access-gate and form-builder. Current marketplace summary: “Turn any public form or API request into a signed, retried, fully-audited webhook to Zapier, Make, n8n, Pipedream, or your own endpoint — without exposing a single admin route.” Marketplace media now includes the extension card plus 7 committed Capell runner PNG captures covering the admin resources and public form. The Zapier discovery JSON capture remains runner evidence only, not buyer-facing product media.
Completed Improvement Slices
Section titled “Completed Improvement Slices”- 2026-06-05: Added real
PublicActionsHealthCheckdiagnostics, aligned cache-safety capability truth, hardened webhook dispatch against DNS rebinding/SSRF, and refreshed marketplace/composer copy for signed, retried, audited public-action webhooks. - 2026-06-06: Captured and committed all 8
docs/screenshots.jsontargets, promoted the 7 styled admin/frontend captures intocapell.jsonmarketplace media, and retained the Zapier discovery JSON capture as runner evidence.
2. Improvements (existing functionality)
Section titled “2. Improvements (existing functionality)”- Resolved: implemented the advertised health checks —
PublicActionsHealthChecknow returns the four manifest diagnostics (webhook-dispatch,webhook-security,provider-presets, andform-builder-integration) and focused coverage proves the checks pass/fail against tables, adapters, presets, and security config. —src/Health/PublicActionsHealthCheck.php,tests/Feature/PublicActionsHealthCheckTest.php— Closed. - Resolved: hardened the webhook SSRF guard against DNS rebinding (TOCTOU) — webhook dispatch resolves the host once through
PublicActionWebhookHostResolver, rejects private/link-local/loopback addresses by default, and pins the validated address into cURL’sCURLOPT_RESOLVEentry before sending. IPv4 and IPv6 pinning are covered. —src/Support/Providers/HttpWebhookPublicActionAdapter.php,src/Data/ResolvedWebhookEndpointData.php,tests/Unit/Support/HttpWebhookPublicActionAdapterTest.php— Closed. - Resolved: pinned the resolved IP and disabled redirect-following on dispatch — the HTTP adapter calls
withoutRedirecting(), sends the originalHostheader, and dispatches with cURL host pinning so unchecked redirects and second DNS lookups cannot reach internal hosts. Redirect and cURL option tests cover the contract. —src/Support/Providers/HttpWebhookPublicActionAdapter.php,tests/Unit/Support/HttpWebhookPublicActionAdapterTest.php— Closed. - Resolved: destination dispatch fan-out is prepared durably before delivery — successful submissions now persist one
pendingdispatch-attempt row per active destination in the same transaction as the handled submission status update. Sync delivery and queued jobs receive that attempt and update it in place, while async jobs are queued withafterCommit(), so a mid-loop failure leaves a recoverable pending row instead of silently dropping a destination. —src/Actions/SubmitPublicActionAction.php,src/Jobs/DispatchPublicActionDestinationJob.php,src/Support/Providers/HttpWebhookPublicActionAdapter.php— Closed. - Resolved: dispatch retries use configurable backoff and jitter —
DispatchPublicActionDestinationJobnow readscapell-public-actions.dispatch_backoff_seconds(default[60, 300, 900]) by attempt number and adds boundeddispatch_retry_jitter_secondsso retryable provider failures do not hammer endpoints at a constant cadence. The legacydispatch_retry_secondsremains a fallback if no valid curve is configured. —config/capell-public-actions.php,src/Jobs/DispatchPublicActionDestinationJob.php— Closed. - Resolved: canonical webhook payload encoding is reused per dispatch —
HttpWebhookPublicActionAdapter::dispatch()builds the body once, encodes it once throughencodedBody(), reuses that canonical string forrequest_hash, HMAC signature headers, and the non-GET request body, and only keeps the structured body for GET query options. —src/Support/Providers/HttpWebhookPublicActionAdapter.php— Closed. - Resolved: schemaless public payloads require explicit opt-in —
capell-public-actions.allow_schemaless_payloadsdefaults false, so actions withoutpayload_schema.fieldsnow reject public submissions instead of silently accepting capped arbitrary payloads. If operators explicitly enable the escape hatch, the existing 16KB cap and warning remain, andPublicActionsHealthCheck::schemalessPayloadCheck()surfaces the relaxed boundary. —config/capell-public-actions.php,src/Actions/SubmitPublicActionAction.php,src/Health/PublicActionsHealthCheck.php— Closed. - Done/Shipped: filled in
docs/overview.md. — The overview now documents package positioning, installed surfaces, public and Zapier routes, admin resources, screenshot coverage, public safety notes, and package verification guidance. —docs/overview.md— S
3. Missing Features (gaps)
Section titled “3. Missing Features (gaps)”- Inbound webhook signature verification — The package signs outbound payloads (
X-Capell-Signature, HMAC-SHA256) but offers no way to verify a signed inbound submission. For asubmit-actionscapability that brands itself on “safe public submissions,” HMAC/Bearer-verified inbound posts (skip-honeypot trusted lane) is the obvious next tier. Ties tosubmit-actions,automation-webhooks. Differentiator. - Captcha breadth beyond Turnstile — Spam protection ships honeypot + Cloudflare Turnstile only. hCaptcha and reCAPTCHA v3 are table-stakes for a paid public-form product. The
PublicActionSpamProtectionAdaptercontract already makes this a small add. Ties topublic-form. Table-stakes. - Resolved: rate-limit identity supports action and token overrides. Public submit throttles now read configurable defaults plus per-action overrides from config or action
settings.rate_limit.per_minute; Zapier API routes authenticate before throttling so provider requests key by integration token, with per-token and provider-level override config. —src/Providers/PublicActionsServiceProvider.php,routes/web.php,config/capell-public-actions.php— Closed. - Idempotency on the Zapier submit path —
SubmitPublicActionActionhonours anIdempotency-Keyheader, but verify the Zapier controller forwards it; Zapier replays, so idempotency here is high-value. Ties tozapier-integration. Differentiator. - Resolved: dead-letter + manual replay for failed dispatches.
ReplayPublicActionDispatchAttemptActionreplays pending, retryable, or failed attempts through the registered destination adapter, and the dispatch-attempt Filament table exposes a confirmed Replay action with success/failure notifications. Successful attempts are not replayable, keeping the admin recovery lever scoped to dead-letter and stuck states. —src/Actions/ReplayPublicActionDispatchAttemptAction.php,src/Filament/Resources/DispatchAttempts/PublicActionDispatchAttemptResource.php— Closed. - Resolved: package no longer advertises
cache-blocking— The shipped behaviour is route-level cache safety: public actionGET/POSTresponses sendno-storeheaders, but the package does not register page cache dependencies for embedded action buttons.capell.jsonnow keepscacheSafety.cacheable:falsefor those routes and drops the unsupportedcache-blockingcapability. —capell.json,tests/Unit/PublicActionsManifestTest.php— Closed. - First-class Form Builder binding UI —
form-builderintegration works only via a hand-editedconfig('capell-public-actions.form_builder.mappings')array consumed bySubmitPublicActionFromFormSubmission. There is no admin UI to bind a form to an action. Ties to thesupports: form-builderdeclaration. Differentiator. - Outbound destination adapters beyond raw HTTP — Only
http_webhookexists; every preset (zapier/pipedream/n8n/make/generic) maps to it. Slack/Discord/email-relay/SQS adapters would justify the “outbound automation” positioning. Ties toautomation-webhooks. Differentiator. - Resolved: submission retention / PII purge command.
capell:public-actions:prune-submissionsprunes submissions older thancapell-public-actions.submission_retention_days(or--days) and their dispatch attempts through the existing cascade. It supports--dry-runfor audit-only counts and--jsonfor scheduled/ops output. —src/Actions/PrunePublicActionSubmissionsAction.php,src/Console/Commands/PrunePublicActionSubmissionsCommand.php— Closed.
4. Issues / Risks
Section titled “4. Issues / Risks”- Health diagnostics are now real (good): the manifest’s 4 declared health checks are implemented by
PublicActionsHealthCheck, and package coverage exercises table presence, adapter registration, preset normalization, and relaxed webhook-guard failures. - DNS-rebinding SSRF window closed (good): dispatch now pins the validated address through cURL, keeps the original
Hostheader, disables redirect-following, and covers private resolution, unchecked redirects, IPv4 pinning, and IPv6 pinning. - Closed: silent partial fan-out. Successful submissions now create pending dispatch-attempt rows before delivery starts, and the HTTP adapter updates those rows in place. Async jobs are queued after commit, leaving a durable pending audit row if a worker or request dies before delivery.
- Closed: schemaless-accept path is opt-in only. Public Actions now rejects actions without
payload_schema.fieldsunlesscapell-public-actions.allow_schemaless_payloadsis explicitly true, and Diagnostics reports that relaxed mode. - Test gaps (medium): strong coverage exists for submit/idempotency/redaction/retry/honeypot/turnstile/redirect-safety/Zapier auth/policies/migrations, health checks, cache-capability truth, and webhook SSRF pinning. Remaining higher-value gaps are fan-out atomicity and performance budget assertions.
tests/. - Performance budget unverified (low):
capell.jsonsetsfrontendRenderBudgetMs:20/adminQueryBudget:40.ShowPublicActionControllerreads only hydratedpayload_schema(no N+1, safe), but no test asserts the budget;dispatchAttempts/submission admin lists over high-volume tables are the real query-budget risk and are unbenchmarked. - i18n (low): public/admin strings are translated (
generic.php,filament.php), but webhook/SSRFInvalidArgumentExceptionmessages are hard-coded English. Acceptable (operator-facing, logged) but worth noting. - Public-output safety (good): verified clean.
ShowPublicActionControllerpasses only{key,label,type,required}; the Blade form leaks no model IDs, handler classes, selectors, or signed URLs; both controllers setno-store, private. Destinationsecret/endpoint_url/headersareencryptedat rest; tokens are stored hashed; webhook error/response summaries are redacted. This is the package’s strongest area. - Tech debt (low): The previously empty
docs/overview.mdis now filled with package surfaces, safety notes, and verification guidance;CHANGELOG.mdrecords dated health, webhook security, canonical payload, overview, and screenshot slices; and webhook dispatch now reuses canonical payload encoding plus configurable retry backoff/jitter.
5. Marketplace & Selling
Section titled “5. Marketplace & Selling”Critique. Marketplace and Composer copy now lead with the concrete, sellable hooks: Zapier/n8n/Make/Pipedream, signed webhooks, encrypted secrets, idempotent submissions, durable retry, and full audit. The static screenshot story is now reconciled for buyer-facing UI: capell.json.marketplace.screenshots lists the extension card plus the 7 committed runner captures for configured actions, action form, destinations, submissions, dispatch attempts, integration tokens, and the frontend form. The Zapier discovery JSON capture stays in the runner contract as technical API evidence until a styled integration explorer or admin UI explains it better.
Improved 1-sentence summary: Turn any public form or API request into a signed, retried, fully-audited webhook to Zapier, Make, n8n, Pipedream, or your own endpoint — without exposing a single admin route.
Improved 3–4 sentence description: Public Actions gives Capell a safe boundary for untrusted public input: submissions are validated against a per-action schema, screened for spam (honeypot + Turnstile), de-duplicated by idempotency key, and persisted before anything fires. Successful submissions fan out to configured destinations over signed HTTP webhooks (HMAC-SHA256) with SSRF protection, encrypted secrets, durable retry, and a complete per-attempt dispatch audit. Native presets cover Zapier, Make, n8n, Pipedream, and generic endpoints, and a registry lets other packages plug in their own handlers, destination adapters, and spam guards. An authenticated Zapier-style JSON API exposes discoverable actions and submissions for no-code automation builders.
Screenshot/media gaps: UI media is closed for the current contract: the 8 captures in docs/screenshots.json exist, and the 7 styled admin/frontend captures are promoted into marketplace media. The raw Zapier discovery JSON screenshot remains docs/runner evidence only. A future architecture diagram (form → validate → handler → fan-out → retry/audit) would still help explain the boundary quickly.
Pricing/tier/bundle positioning. Correctly premium in the automation bundle. In practice this is infrastructure / a dependency as much as a standalone seller — form-builder, access-gate, and future intake packages will all submit through it. Position it as the shared automation backbone of the Automation bundle (bundle pull-through), and cross-sell: Form Builder (no-code form → action binding), Access Gate (gated submissions), Email Studio (notify-on-submission). Extension Suites angle: publish the PublicActionHandler / PublicActionDestinationAdapter / PublicActionSpamProtectionAdapter contracts as the SDK other suites build on.
Differentiators / value props / target buyer. Differentiators: SSRF-guarded + signed + encrypted webhooks, idempotency, and a durable dispatch-attempt audit — most “form-to-webhook” plugins have none of these. Target buyer: agencies/site owners wiring CMS submissions into existing automation stacks (Zapier/Make/n8n) who need it to be safe and auditable, and package developers who want a vetted public-input boundary instead of rolling their own controllers.
Keywords/tags: webhooks, zapier, n8n, make, pipedream, automation, public-forms, server-side-actions, hmac-signed-webhooks, idempotency, ssrf-protection, integration-api, spam-protection, outbound-dispatch.
6. Prioritized Roadmap
Section titled “6. Prioritized Roadmap”| Item | Bucket | Effort | Impact | Section ref |
|---|---|---|---|---|
| Implement the 4 advertised health checks (truth-in-manifest) | Done | M | High | 2, 4 — closed by PublicActionsHealthCheck diagnostics and focused health coverage |
| Close DNS-rebinding TOCTOU + disable/guard webhook redirects | Done | M | High | 2, 4 — closed by cURL host pinning, redirect blocking, and IPv4/IPv6 pinning coverage |
| Add SSRF rebinding + redirect-follow tests; add health-check test | Done | M | High | 4 — closed by webhook redirect/pinning tests and health-check diagnostics coverage |
| Ship the 8 specced screenshot targets; promote only the 7 styled UI captures | Done | S | High | 5 |
| Shipped: sharpen marketplace summary/description | Done | S | High | 5 |
Resolve cache-blocking (wire real dependency blocking or drop capability) | Done | S | Med | 3, 4 — closed by dropping unsupported capability |
| Done/Shipped: Make destination fan-out durable/atomic (after-commit + pending rows) | Done | M | High | 2, 4 — successful submissions create pending dispatch-attempt rows before sync or queued delivery starts. |
| Gate schemaless submissions behind explicit opt-in config | Done | S | Med | 2, 4 |
| Done/Shipped: Add manual dispatch replay + dead-letter admin surface | Done | M | High | 3 — pending, retryable, and failed dispatch attempts can be replayed from the admin dispatch-attempt table. |
| Configurable exponential backoff + jitter on dispatch job | Done | S | Med | 2 |
| Encode payload once per dispatch (hash/sign/send reuse) | Done | S | Low | 2 |
| Done/Shipped: Per-token + per-action rate-limit overrides | Done | M | Med | 3 — public submit and Zapier API throttles now support configured defaults plus action, provider, and token overrides. |
| Done/Shipped: Submission retention / PII purge command | Done | M | Med | 3 — capell:public-actions:prune-submissions supports configured retention, --days, --dry-run, and JSON output. |
| hCaptcha + reCAPTCHA v3 spam adapters | Later | M | Med | 3 |
| Form Builder → action binding admin UI | Later | M | High | 3 |
| Non-HTTP destination adapters (Slack, email-relay, SQS) | Later | L | High | 3 |
| Inbound signed-webhook verification (trusted submission lane) | Later | M | High | 3 |
Shipped: Fill docs/overview.md | Done | S | Low | 2, 4 |
Shipped: Add real CHANGELOG entries | Done | S | Low | 4 |