=== Octa Forms ===
Contributors: octadevs
Tags: forms, contact form, webhook, gdpr, rest api
Requires at least: 6.9
Tested up to: 7.0
Requires PHP: 8.0
Stable tag: 1.4.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.txt

Flexible WordPress forms, delivered by email and signed webhooks. Drop in the shortcode or drive the REST API from your own theme.

== Description ==

OctaForms is a forms plugin for WordPress with two ways to use it. Drop a form on any page with the `[octa_form]` shortcode and no code. Or build against a small REST API and let your own theme render the markup. Either way the plugin does the hard part. It validates every submission, stores it safely and delivers it by email and signed webhook.

There is no drag-and-drop builder on purpose. You get clean, fast forms and a delivery engine you can trust, not another page builder. The whole front end is under 10 KB of JavaScript and CSS over the wire, gzipped, and it never blocks your page from rendering.

= Ready to use, no code needed =

* The `[octa_form slug="…"]` shortcode renders any form you define, on a page or in a widget area.
* A starter contact form is created for you on first run, ready to wire up to your inbox.
* On top of that, an optional contact widget. A floating button opens a small popup with up to three tabs: Call us, Request a callback and Leave a message. Each tab appears only when you have set it up.
* File uploads. Add a file field and visitors can attach a document or a photo. A file that is too big or of the wrong type is caught before the upload starts, a progress bar shows how far a large one has got, and the file arrives attached to your notification email. The limit is 10 MB per file, or your server's own limit when that is lower.
* Uploaded files are stored outside the media library, in a protected folder under unguessable names, so nobody reaches them by pasting a URL. Delete a submission and its files go with it.
* Ready for translation. Every string goes through the WordPress translation system, so the plugin picks up whatever your site language provides.

= Built for developers =

* Public REST API: a token endpoint and a per-form submissions endpoint. The form slug is a stable part of the contract and locks after you publish.
* Form definitions live as JSON on a private post type. You control the fields, validation rules, emails, webhooks and settings.
* A dry-run test panel. Preview validation, emails and webhook payloads for any form without sending an email or saving a submission.
* A validation engine with built-in rules (`required`, `email`, `tel`, `min`, `max`, `date`, `regex` and more). Add your own with the `octa_forms_validation_rules` filter.
* Multiple signed webhooks per form, with SSRF protection that re-checks DNS before every send. Each request carries an HMAC signature in the `X-Octa-Forms-Signature` header, computed over the raw body.
* Keep your forms in your theme. A form definition can live as a JSON file in `{theme}/data/octa-forms/`, so it is versioned with the rest of your code and travels between staging and production. Import it from the admin or with `wp octa-forms theme import`. The database stays the source of truth at runtime, the files never are.
* An optional vanilla JavaScript helper that implements the full token and retry contract for you. No framework, no build step.
* WP-CLI commands for the delivery queue and for syncing form definitions from your theme.
* Nothing hidden behind a build step. The JavaScript and CSS sources ship inside the plugin, in `resources/`, next to the compiled files in `build/`. Run `npm install` and `npm run build` to rebuild them yourself and compare.
* Thorough documentation. The full guides live at https://forms.octadevs.com/, covering the REST contract, theme integration, webhooks and every hook.

= Reliable delivery =

OctaForms puts durability before timeliness. Every delivery is saved to a queue before the visitor gets their response. The first email attempt runs inside the request and is guarded by a circuit breaker. Anything that fails is retried by cron with exponential backoff. Nothing is ever silently lost. Every protection and every degradation leaves a trace you can read in the admin or in Site Health.

The queue also keeps itself moving. On a site where WordPress cron is starved by a page cache or a blocked loopback, ordinary visitor traffic quietly tells the queue to send what is waiting. A notification does not sit for hours because nothing happened to trigger cron. It is a safety net rather than a replacement for a real system cron, it never hides a broken cron from Site Health, and you can switch it off in Settings.

= Anti-spam that does not lose real leads =

* A honeypot with a spam quarantine. Suspected spam is stored, not dropped, so you can review it and recover a real submission. A sudden spike usually means you are catching real people, for example browser autofill.
* Two-counter rate limiting. A flood of requests earns a short, temporary ban. A burst of genuine successful submissions from one network, like an office behind a single IP during a campaign, gets a polite "try again shortly" instead of a ban.
* A stateless signed token that adds deduplication, a time trap and a JavaScript requirement. It is a helper, not your main defence. The honeypot and rate limiting are.
* Atomic deduplication. A double click returns the same submission and each delivery goes out once.

= Measure what your forms do =

* Form events reach Google Tag Manager on their own. A form start, a submission, a success, an error and every contact widget interaction land in the dataLayer, ready for you to build a conversion trigger on. You do not have to touch any code to get them there.
* The events describe the interaction, not the person. They carry the form, the place on the site and the outcome, and no visitor data at all. Nothing is sent anywhere until you build a trigger in your own tag manager, so the decision to measure stays yours.
* Every event is also available as a plain JavaScript event on the form, so your theme can react to a submission without a tag manager in the picture.
* An optional visit path records which pages someone looked at before they got in touch, so you can see what actually brought them to you. It is off by default, it stores page paths only and never the full address, and it records nothing until the visitor has agreed to analytics.

= GDPR =

* A consent snapshot stored with each submission: the field, the text that was shown, the value and the time.
* The visitor IP is stored only as a hash, kept for spam forensics. By default it never leaves your site. It is not in webhooks and not in exports. The one exception is the optional Akismet anti-spam check. If you turn it on, the submission content along with the visitor's IP address and email are sent to Akismet to be scored for spam.
* Full integration with the WordPress "Export" and "Erase" personal data tools, covering submissions, their delivery records and the visit path.
* Retention with automatic cleanup, global or per form, with a matching cleanup of delivery records and of any files that came with a submission. The spam quarantine always clears after 30 days.
* CSV export limited to received submissions by default, hardened against spreadsheet formula injection.
* Optional full data removal on uninstall.

== External services ==

By default OctaForms sends no data to any third party. It stores and delivers on your own site, and the plugin makes no outbound request until you turn the Akismet check on yourself.

= Akismet spam checking (optional, off by default) =

Akismet is a spam filtering service run by Automattic. OctaForms can send each submission to it for a spam score, so that obvious spam is quarantined instead of landing in your inbox. Nothing is sent unless you tick "Check submissions with Akismet" in Settings and save an Akismet API key.

The plugin contacts two endpoints:

* `https://rest.akismet.com/1.1/verify-key` when you save the settings page with a key. It sends the API key and your site address, so a wrong key is reported to you straight away.
* `https://{your-key}.rest.akismet.com/1.1/comment-check` once per submission, while the submission is being processed. It sends the text the visitor typed, their email address, their IP address, their browser's user agent and referrer, and your site address. Uploaded files are never sent.

If Akismet is slow or unreachable the submission goes through anyway, so a real lead is never lost to a failed check.

Terms of service: https://akismet.com/tos/
Privacy policy: https://akismet.com/privacy/
Automattic privacy policy: https://automattic.com/privacy/

== Installation ==

1. Upload and activate the plugin.
2. Create a form under **OctaForms**. Set the slug (it becomes part of the API contract and locks after you publish) and edit the config. Use the built-in **test submit** panel to preview validation, emails and webhook payloads without sending anything.
3. Put a form on your site. Drop `[octa_form slug="…"]` on a page, turn on the contact widget, enable the JavaScript helper, or connect your own theme to the REST API.

= Production checklist =

* **Cron.** WP-Cron only fires on traffic. For reliable delivery, run a real system cron every minute: `wp octa-forms queue drain`. Site Health turns red when the queue worker has not run for 15 minutes or more. Until you set that up, visitor traffic nudges the queue along so nothing sits for hours, but it is a safety net and it does not replace a proper cron.
* **Email deliverability.** A successful `wp_mail()` means the transport accepted the message, not that it arrived. Use an SMTP plugin and send from a domain with valid SPF and DKIM. Put replies to the visitor in Reply-To, never in From.
* **Cache.** Exclude the plugin's REST paths (`/wp-json/octa-forms/v1/`) from any page or CDN cache. The responses already send no-store headers, but your cache layer needs its own rule, especially when it does not respect `DONOTCACHEPAGE`.
* **Behind a proxy or CDN.** Set the client IP source in settings and list your provider's ranges as trusted. Otherwise the header is ignored, because it would be easy to spoof. Firewall your origin so traffic can only arrive through the CDN.
* **Webhooks are at-least-once.** Receivers must deduplicate by the payload `id` and verify the `X-Octa-Forms-Signature` against the raw body.
* **External monitoring.** An uptime monitor can watch the queue over `GET /wp-json/octa-forms/v1/queue/health`. The endpoint requires an administrator, so give the monitor a WordPress application password. Developers can widen access with the `octa_forms_queue_health_access` filter.

== Frequently Asked Questions ==

= Where do I build the form fields? =

In the form config, a schema-validated editor with one-step undo. OctaForms has no drag-and-drop builder on purpose. It is aimed at people who want clean forms and control over the output, not a visual page builder.

= Why did a real submission land in the spam quarantine? =

Almost always browser autofill filling the honeypot field. Recover it from **Submissions**, where recovery also sends its deliveries. Then rename the honeypot field to something autofill will not target. The config editor warns about risky names.

= The form says the security token is unreachable =

A security plugin or firewall is blocking `/wp-json/` for visitors. Site Health runs a self-test of the token endpoint and reports this. As a last resort a form can set `"requireToken": false` in its config. The honeypot and rate limiting stay active.

= Emails are marked "sent" but never arrive =

"Sent" only means your server's transport accepted the message. Install an SMTP plugin and set up SPF and DKIM for the sending domain.

== Screenshots ==

1. The form editor. Add fields, set the rules an answer has to pass, and copy the shortcode that puts the form on a page.
2. One submission in full. The answers, what was sent out and when, and the pages the visitor looked at before getting in touch.
3. Everything that came in. Spam is kept apart so you can review it, and you can export what you need to CSV.

== Changelog ==

= 1.4.0 =
* Answers are cleaned before they are saved. HTML typed into a field is removed, so what reaches your inbox is the text someone wrote and nothing else. Line breaks in a long message are kept. An email address with a typo is still reported back to the visitor rather than quietly changed into a different address.
* Stricter handling of uploaded files. The plugin now opens only files that WordPress itself received as an upload, and the file name and type reported by the browser are cleaned before anything uses them.
* ZIP archives are no longer accepted by file fields out of the box. Nobody can tell what is inside an archive, and the file goes on to your inbox as an attachment. PDFs, images, Office and OpenDocument files, text and CSV are unchanged. If you do collect archives, a developer can add the type back with the `octa_forms_allowed_file_types` filter.
* Form definitions in your theme are now imported only. The plugin no longer writes a JSON file back into the theme after an edit in the admin, because a plugin has no business writing into a theme. To move an admin edit into your repository, copy the config JSON out of the editor. Import from a file, the status view and the WP-CLI commands all work as before.
* The list of trusted proxy addresses is now checked when you save it. An entry that is not a valid address or range is dropped and named, instead of being stored and silently never matching.
* Translations now come from WordPress.org for every language, through the normal update system, instead of shipping inside the plugin.
* Documented in detail which data the optional Akismet check sends and where, with working links to its terms and privacy policy.
* Internal tidying of how the admin screens load their styles.

= 1.3.1 =
* Clearer errors on a form. A required field that is left empty now says so, instead of complaining about the format of an answer nobody gave.
* Several messages on the same field are easier to read, with proper separation between them.
* Tidier translation files.

= 1.3.0 =
* Choose which checkbox answers are kept as proof. A new "Record consent" switch decides that per field, so an optional marketing opt-in is stored while a plain "I confirm my details" box is not.
* Better file uploads. A file that is too big or of the wrong type is caught before the upload starts, and a progress bar shows how far a large one has got. Empty files get their own clear message, you are warned before leaving a page mid-upload, and older Office files (.doc and .xls) are recognized more reliably.
* Form events reach Google Tag Manager on their own. Starts, submissions, successes, errors and contact widget interactions land in the dataLayer, ready for you to build triggers on. No visitor data is included and nothing is sent anywhere until you set a trigger up.
* Delivery keeps trying for longer. After a mail server or webhook outage, OctaForms now retries for several hours instead of giving up within minutes.
* Clear a backlog fast. The delivery queue has a new "Retry" bulk action for requeuing many failed messages at once.
* Fewer false alarms about the queue. The "is the queue running?" checks stay quiet on a low-traffic site with nothing to send, and speak up only when messages are genuinely stuck.
* Clearer confirmation handling. A mistyped address in a confirmation field now leaves a visible "held back" note instead of quietly sending nothing.
* Earlier form checks. The config editor now flags a field name that clashes with the system, and a maximum length larger than one submission can carry.
* Many smaller reliability fixes across the form, the delivery queue and the admin screens.

= 1.2.0 =
* Optional Akismet spam checking. Add your key in Settings and every submission is scored before it is saved. If Akismet is slow or unreachable the submission still goes through, so a real lead is never lost to a failed check.
* The submissions list now leads with the sender's email address and shows only the fields worth a glance.
* The submit button shows a spinner while a form is on its way.
* Plain text emails are laid out more readably.
* A large round of security and reliability work across the whole plugin, from the intake pipeline to the admin screens.

= 1.1.0 =
* See where a visitor has been. An optional visit path records the pages someone viewed before they submitted. It is off by default and it only ever records anything once the visitor has agreed to analytics.
* Test a webhook from the editor. Send a sample payload to your endpoint and read the response, without a real submission. Payloads now carry the form name as well.
* Long consent text next to a checkbox, with support for links.
* You get told when a message is lost. A delivery that fails for good now shows up in the dashboard and in Site Health, so a missed notification never slips by unnoticed.
* A field that fails validation is now marked as invalid for screen readers, and styled to match.
* Polish translation.

= 1.0.0 =
* First public release.
* Two ways to use it: the `[octa_form]` shortcode with no code, or a REST API for your own theme.
* REST intake pipeline: security token, rate limiting, honeypot quarantine, validation and atomic deduplication.
* Durable delivery queue with an inline first attempt and a circuit breaker.
* Email notifications, autoresponders and HMAC-signed webhooks with SSRF protection.
* A thin admin: config editor, dry-run test submit, submissions with spam quarantine, queue view, hardened CSV export and settings.
* GDPR tools: export, erase, retention and consent snapshots.
* An optional floating contact widget for phone, callback and message.
* A vanilla JavaScript helper, WP-CLI commands and Site Health checks.

== Upgrade Notice ==

= 1.4.0 =
File fields stop accepting ZIP archives, and form definitions in your theme are now imported only. Translations move to WordPress.org.

= 1.3.1 =
Clearer validation messages on your forms. A required field that is left empty now says exactly that.

= 1.3.0 =
Consent is now recorded per checkbox, uploads show progress and catch bad files early, and form events reach Google Tag Manager out of the box. Delivery also survives a multi-hour outage and lost messages are easier to find and retry.

= 1.2.0 =
Adds optional Akismet spam checking, a clearer submissions list, and a large round of security and reliability work.

= 1.1.0 =
Adds an optional visit path, webhook testing from the editor, long consent text on checkboxes and clearer reporting of failed deliveries.

= 1.0.0 =
First public release of OctaForms.
