Guide to Gravity Forms Webhooks: Features and Best Practices
Gravity Forms webhooks send each form submission from your WordPress site to another service the moment it is submitted. If you collect leads, orders, applications or support requests with Gravity Forms, the Webhooks Add-On lets you push those entries into a CRM, a database or your own API without a dedicated add-on for each one.
This guide covers how Gravity Forms webhook feeds are configured, what the request looks like, how to authenticate requests when Gravity Forms doesn't sign them, and the best practices for receiving form submissions in production.
What are Gravity Forms webhooks?
The Gravity Forms Webhooks Add-On "enables you to make remote HTTP requests to external services when a form is submitted". You set it up per form, as a feed. Each feed has its own URL, method, format, headers and body, and you can add several feeds to one form.
A feed fires on one thing: a submission of its form. There are no event types to choose from. You can narrow a feed with a Webhook Condition, so it only runs when the submitted values match, but without one "the Webhook will be executed for every form submission".
The requests come from your own WordPress server, not from a Gravity Forms service. Gravity Forms does not sign them. The only way to authenticate them is a header you add to the feed yourself.
Gravity Forms webhook features
| Feature | Details |
|---|---|
| Configuration | Per form: Form Settings > Webhooks > Add New |
| Trigger | A form submission, optionally filtered by a Webhook Condition |
| Request URL | Supports merge tags. Must pass WordPress core URL validation |
| Methods | GET, POST, PUT, PATCH, DELETE |
| Formats | FORM or JSON |
| Content-Type | Set to application/json automatically for JSON with POST or PUT |
| Request body | All Fields (the entire unformatted entry) or Select Fields (keys you map) |
| Authentication | None built in. Add a secret in Request Headers |
| Signature | None |
| Event type | None |
| Retries, timeout, delivery ID | Not documented |
| Sender | Your WordPress server |
The payload
With All Fields, Gravity Forms sends "the entire unformatted entry". The webhook page doesn't show it, but Gravity Forms documents the entry object separately: it is keyed by field ID, with each field's value under its ID, and sub-inputs such as the parts of a name field under IDs like 1.3 and 1.6. Alongside the field values, the entry carries properties such as id, form_id, date_created, source_url, ip, user_agent and status. A JSON body for a simple contact form looks something like this:
{
"id": "101",
"form_id": "3",
"date_created": "2026-10-21 10:15:30",
"source_url": "https://example.com/contact/",
"status": "active",
"1.3": "Ada",
"1.6": "Lovelace",
"2": "ada@example.com",
"3": "Please call me back."
}
Gravity Forms doesn't publish a webhook body, so treat this as illustrative and check a real delivery before you rely on value types. date_created is UTC in YYYY-MM-DD HH:MM:SS format, and field IDs depend on your form, so check them in the form editor.
With Select Fields, you choose the keys and map each one to a form field, an entry meta value or a custom value with merge tags. Only those keys are sent. If you use this mode, include id and form_id so you can deduplicate and route.
The Request Format decides the encoding. JSON sends a JSON body. FORM sends form fields, so your receiver must accept both if you mix feeds.
Setting up Gravity Forms webhooks
- Install and activate the Webhooks Add-On.
- Generate a random secret, for example with
openssl rand -hex 32, and store it on your receiving server asGRAVITY_.FORMS_ WEBHOOK_ SECRET - In the form, go to Form Settings > Webhooks and click Add New.
- Enter a Name and your Request URL, set Request Method to
POSTand Request Format toJSON. - Under Request Headers, add a header named
X-Webhook-Secretwith your secret as its value. Gravity Forms doesn't prescribe a name, so any header works as long as your receiver checks the same one. - Choose All Fields for the request body, or Select Fields and map the keys you need.
- Add a Webhook Condition if only some submissions should be sent, then save the feed and submit the form to test it.
Gravity Forms checks the Request URL with WordPress's wp_http_validate_url, and "feed processing will be aborted if the URL uses a custom port or redirects to another URL that fails validation". Use a plain HTTPS URL on the standard port.
For local development, the Hookdeck CLI (hookdeck listen 3000 gravity-forms --path /webhooks/gravity-forms) gives you a public HTTPS URL that forwards to your local server, with no account required. Use that as the Request URL.
Securing Gravity Forms webhooks
With no signature, authentication means comparing the header you configured with the value you stored, in constant time, and rejecting the request if it is missing or wrong. Fail closed: if the secret is not set on your server, reject everything. Serve the endpoint over HTTPS only, because the secret travels with every request.
Because nothing is computed over the body, you don't need the raw bytes to authenticate. Checking the header before parsing is still a good habit.
const crypto = require('crypto');
const express = require('express');
function verifyGravityFormsSecret(headerValue, secret) {
if (!secret || typeof headerValue !== 'string') return false; // fail closed
const received = Buffer.from(headerValue, 'utf8');
const expected = Buffer.from(secret, 'utf8');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
const app = express();
app.post('/webhooks/gravity-forms', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyGravityFormsSecret(req.get('x-webhook-secret'), process.env.GRAVITY_FORMS_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const body = req.body.toString('utf8');
const entry = req.is('application/json')
? JSON.parse(body)
: Object.fromEntries(new URLSearchParams(body)); // FORM feeds
// Route on entry.form_id, store, then answer.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
def verify_gravity_forms_secret(header_value, secret) -> bool:
if not secret or not isinstance(header_value, str):
return False # fail closed
return hmac.compare_digest(header_value.encode("utf-8"), secret.encode("utf-8"))
# In FastAPI:
# verify_gravity_forms_secret(request.headers.get("x-webhook-secret"),
# os.environ.get("GRAVITY_FORMS_WEBHOOK_SECRET"))
A shared header proves the caller knows the secret. It does not protect the body from changes in transit, which is another reason to use HTTPS.
Make Gravity Forms webhooks production-ready. Hookdeck Event Gateway checks your secret header on every submission, stops requests that fail before they reach your service, and durably queues each entry.
Gravity Forms webhook limitations and pain points
No signature, no built-in secret
The Problem: Anyone who finds your webhook URL can post fake submissions.
Why It Happens: The Webhooks Add-On has no signing option and no secret setting. Its only security-related setting is Request Headers.
Workarounds:
- Add a long random secret as a request header on every feed, and check it on every request.
- Use HTTPS so the header cannot be read in transit.
- Rotate the secret by adding the new value on your server first, accepting both briefly, then updating the feeds.
How Hookdeck Can Help: Hookdeck's Gravity Forms source asks for the header name and its exact value, and stops requests that fail before they reach your service. Every request, whether it passes or fails, is logged for inspection.
Submissions lost when your endpoint is down
The Problem: Your endpoint is down for a deploy and the submissions made during that time never arrive.
Why It Happens: Gravity Forms documents no retries, timeout or success rule for webhook feeds. The request is made by your WordPress site during feed processing, and nothing in the add-on's documentation sends it again if it fails.
Workarounds:
- Keep entries stored in Gravity Forms so you have a record of what was submitted.
- Reconcile periodically against those stored entries to find any your receiver missed.
- Keep your endpoint simple so it rarely fails.
How Hookdeck Can Help: Point the feed at a Hookdeck source. Hookdeck accepts the request immediately and stores it, then delivers it to your service with retries when it is available, so a deploy no longer costs you submissions.
Field IDs instead of names
The Problem: The payload is full of keys like "2" and "3.3", and it breaks when someone edits the form.
Why It Happens: All Fields sends the unformatted entry, which is keyed by field and input ID rather than by label.
Workarounds:
- Use Select Fields and map stable names, such as
emailandfirst_name, to the fields you need. - Document the field IDs your receiver depends on next to the form.
- Validate required keys and reject or flag entries that miss them.
How Hookdeck Can Help: A Hookdeck transformation can rename field IDs to stable keys in one place before the entry reaches your service.
FORM and JSON on the same endpoint
The Problem: One feed works and another sends a body your handler can't parse.
Why It Happens: Format is a per-feed choice. JSON feeds with POST or PUT get Content-Type: application/json. FORM feeds send form fields instead.
Workarounds:
- Standardise on
JSONwithPOSTfor every feed. - If you can't, branch on
Content-Typeas the example above does.
How Hookdeck Can Help: Hookdeck logs each request's headers and body exactly as received, so you can see which feed sent which format.
Best practices
Route on form_id
There is no event type, so route on the form. With All Fields, form_id is in the entry. With Select Fields, map it in, or give each form's feed its own URL.
Deduplicate on the entry ID
Store the entry id and skip ones you have already handled. With Select Fields, map id in, or you will have nothing reliable to key on. See our guide to webhook idempotency.
Keep the request fast
Gravity Forms documents no timeout, so don't depend on a generous one. Store the entry and answer straight away, then do slower work, such as CRM calls, in a background job. See why to process webhooks asynchronously.
Use conditions instead of filtering in code
If only some submissions matter, such as one product or one country, set a Webhook Condition on the feed so the others are never sent.
Conclusion
Gravity Forms sends a webhook per form submission through a feed you configure, with no signature and no event type. Add a secret request header and check it in constant time, send JSON with POST, route on form_id, deduplicate on the entry id, and plan for the fact that a failed request is not documented as retried.
Hookdeck Event Gateway checks the secret header, stores each submission the moment WordPress sends it, and delivers it to your service with retries, so downtime on your side doesn't lose form entries.
Get started with Hookdeck for free and handle Gravity Forms webhooks reliably in minutes.