Gravity Forms Webhooks: Send Gravity Forms Submissions to n8n (Step-by-Step)
Gravity Forms doesn't include built-in webhook output in its base plugin. To send form entries to tools like n8n you need custom code or an additional plugin. This guide covers both approaches — plus how to map numeric field IDs to clean names using the REST API.
TL;DR
- The basic Gravity Forms webhook uses
gform_after_submission+wp_remote_post— works for low volume but silently drops deliveries on endpoint failure - Access entry fields with
rgar($entry, 'field_id')— field IDs match the field numbers shown in the Gravity Forms builder - For production: add persistence and retry logic so no form submission is silently dropped when the receiving endpoint fails
Basic Gravity Forms Webhook Using wp_remote_post
The most direct approach to Gravity Forms webhooks uses the gform_after_submission action hook. Gravity Forms fires this hook after a successful submission — you hook into it, read the entry data using the rgar() helper, and POST it to your webhook endpoint.
Field values are stored under their numeric field ID in the $entry array. Field 1 is rgar($entry, '1'), field 2 is rgar($entry, '2'), and so on. Checkbox sub-fields use dot notation: rgar($entry, '6.1'). (rgar() docs)
Here's the minimal working code. Add it to your theme's functions.php or a custom plugin:
functions.php — gravity forms webhook (basic)
add_action('gform_after_submission', function ($entry, $form) { $name = rgar($entry, '1'); // Full Name (field ID 1) $email = rgar($entry, '2'); // Email Address (field ID 2) $phone = rgar($entry, '3'); // Phone Number (field ID 3) $subject = rgar($entry, '4'); // Subject (field ID 4) $message = rgar($entry, '5'); // Message (field ID 5) wp_remote_post('https://your-n8n-url/webhook/test', [ 'headers' => ['Content-Type' => 'application/json'], 'body' => json_encode([ 'name' => $name, 'email' => $email, 'phone' => $phone, 'subject' => $subject, 'message' => $message, ]), ]); }, 10, 2);
This works. It's simple and widely used. For a hobby project or an internal form with low submission volume, it does the job.
The problem is what happens in production.
Why the Basic Gravity Forms Webhook Setup Breaks in Production
The wp_remote_post() approach works in development. The problem shows up once your Gravity Forms webhook is handling real traffic: the call is synchronous, so PHP blocks and waits for the remote server to respond before the form submission completes.
Stripe's webhook best practices documentation recommends that receivers respond with HTTP 200 immediately — before processing — precisely because synchronous callers will time out and drop the event if the acknowledgement is too slow. The same principle applies to any WordPress form sending webhook data: the sender cannot wait for the receiver to finish its work.
- × No retries if the webhook fails. If n8n is restarting, rate-limiting you, or simply returns a 500 — the delivery attempt is gone. The form appeared to submit successfully but your workflow never ran.
- × No logging. There's no record of what was sent, when, or what response came back. Debugging a missed submission means checking server logs and hoping the data is still there.
- × No visibility into failures. You won't know a Gravity Forms submission was lost until a lead follows up asking why no one responded. By then it's too late.
- × No way to replay submissions. Once the HTTP call fails, the data is gone from the delivery context. You can't re-send it without the user resubmitting the form.
- × Slows down form submission for the user. If your n8n webhook takes 3 seconds to acknowledge, every Gravity Forms submission takes 3 extra seconds. If it times out (default: 5s), users see a hang.
Gravity Forms submissions can fail silently if the receiving service is down. Without retries, that means lost leads — no way to know it happened until someone complains.
Reliable Method: Queue-Based Webhooks with Retry and Logging
A reliable Gravity Forms webhook setup separates two things the basic approach conflates: recording that a submission happened and delivering it to the endpoint.
When a Gravity Forms entry is created, a background job is queued immediately. The user gets an instant response — the form submission is complete from their perspective. A cron worker then picks up the job and attempts delivery in the background.
If the delivery fails — because n8n returned a 5xx, hit a rate limit (429), or simply timed out — the job is scheduled for retry with exponential backoff: 1 min, 2 min, 4 min, 8 min, 16 min. Each attempt is logged with the HTTP status code and response body. You can see every success and failure in the WordPress admin.
If all retry attempts are exhausted, the job enters a failed state — visible in the event log, and replayable via the REST API or the admin UI. No data is lost.
Step-by-Step: Gravity Forms Webhook Setup
with Retry and Logging
- Install the plugin
Search for this exact description in Plugins → Add Plugin — it narrows the WordPress.org search to exactly one result:
search text — paste into WordPress plugin search
FlowSystems
- Create a new webhook
Go to Webhooks → Add Webhook in the WordPress admin. Give it a name (e.g., "Gravity Forms → n8n").
Create a new Gravity Forms webhook - Select the trigger
Set the WordPress action hook to
gform_after_submission. This fires once per successful Gravity Forms entry — after validation passes and the entry is stored in the database.action hook — paste into the trigger field
gform_after_submission
- Set the webhook URL
Paste your n8n webhook URL (e.g.,
https://your-n8n-url/webhook/test). The plugin will POST the full Gravity Forms entry to this endpoint on every submission. - Save and test
Submit your Gravity Forms form. Check the Event Log in the plugin admin to see the delivery status and the raw payload that was sent.
Gravity Forms webhooks in action — form submission, Event Log delivery status, and raw payload in log details - Optional: customize the payload with field mapping
Use the Payload tab in the webhook settings to pick exactly which Gravity Forms fields are sent and rename keys — no code required.
Gravity Forms webhooks payload mapping — selecting and renaming fields via the plugin UI
Setting Up the n8n Webhook
On the n8n side, you need a Webhook node configured to receive POST requests:
- Add a Webhook node
In your n8n workflow, add a new node and search for "Webhook".
- Set HTTP Method to POST
The plugin sends a JSON POST request, so make sure the Webhook node is set to accept
POST. - Copy the webhook URL
n8n will generate a URL like
https://your-n8n-url/webhook/126cc22b-ba78-4d37-b3b9-2e72eb2edc7e. Copy this and paste it into the plugin's webhook URL field (step 4 above).n8n Webhook node — copy the Production URL and paste it into the plugin's webhook URL field - Activate and test
Click "Listen for test event" in n8n, then submit your Gravity Forms form. n8n Webhook node will receive the incoming request with all configured headers & payload.
Full walkthrough — Gravity Forms submission triggers a webhook payload delivered to n8n via Webhook Actions by Flow Systems
Example Payload
Here's what a typical Gravity Forms submission looks like when it arrives at your n8n webhook. The args property is an array — args[0] is the entry, args[1] is the full form definition (fields, confirmations, notifications) and is omitted here for brevity.
Field values are stored under their numeric field ID as string keys: "1" is Full Name, "2" is Email, and so on.
POST body — application/json (args[1] form definition omitted)
{ "event": { "id": "efab972c-09bc-460b-9ec3-ad63484b3b14", "timestamp": "2026-04-01T15:30:47Z", "version": "1.0" }, "hook": "gform_after_submission", "args": [ { "id": "3", "status": "active", "form_id": "1", "ip": "172.22.0.1", "source_url": "https://your-wordpress-site.com/gravity-forms-example/", "currency": "USD", "date_created": "2026-04-01 15:30:47", "created_by": "1", "1": "Mateusz", "2": "[email protected]", "3": "(150) 010-0900", "4": "general", "5": "Hey, this is example test form message!" } ], "timestamp": 1775057447, "site": { "url": "https://your-wordpress-site.com" } }
In n8n, use {{ $json.args[0]["1"] }} to access the name field, {{ $json.args[0]["2"] }} for email, and so on. The numeric key names are unwieldy in workflows — the REST API section below shows how to apply field mapping so your n8n nodes receive name, email, etc. instead.
Field Mapping via REST API
The plugin captures the raw payload from the first real form submission and lets you define a field mapping — translating dot-notation paths in the payload (like args.0.1) to clean output keys (like data.name). After mapping, every future delivery arrives at n8n with readable field names instead of numeric IDs.
All requests use your API token in the Authorization header. Replace <your-token> below with your actual token from Webhooks → API Keys in the WordPress admin.
- List all webhooks — find the Gravity Forms one
Start by listing all configured webhooks to get the webhook ID:
curl — GET /webhooks
curl -s https://your-wordpress-site.com/wp-json/fswa/v1/webhooks \ -H "Authorization: Bearer <your-token>" | jq .response
[ { "id": "22", "name": "Gravity Forms to n8n example", "endpoint_url": "https://n8n.your-site.com/webhook/126cc22b-ba78-4d37-b3b9-2e72eb2edc7e", "auth_credential_id": 3, "is_enabled": true, "triggers": ["gform_after_submission"] } ]
The webhook ID is
22. Use this in the next steps. - Get webhook config
Fetch the full config for webhook ID 22 to confirm trigger and endpoint:
curl — GET /webhooks/22
curl -s https://your-wordpress-site.com/wp-json/fswa/v1/webhooks/22 \ -H "Authorization: Bearer <your-token>" | jq .response
{ "id": "22", "name": "Gravity Forms to n8n example", "endpoint_url": "https://n8n.your-site.com/webhook/126cc22b-ba78-4d37-b3b9-2e72eb2edc7e", "auth_credential_id": 3, "is_enabled": true, "created_at": "2026-03-30 20:20:58", "triggers": ["gform_after_submission"] }
- Fetch the captured payload
After the first real form submission fires, the plugin captures the raw payload. Fetch it to see the exact dot-notation paths to use in your field mapping:
curl — GET /schemas/webhook/22/trigger/gform_after_submission
curl -s "https://your-wordpress-site.com/wp-json/fswa/v1/schemas/webhook/22/trigger/gform_after_submission" \ -H "Authorization: Bearer <your-token>" | jq .example_payloadresponse (example_payload — trimmed)
{ "hook": "gform_after_submission", "args": [ { "id": "3", "form_id": "1", "1": "Mateusz", "2": "[email protected]", "3": "(150) 010-0900", "4": "general", "5": "Hey, this is example test form message!" } ] }
The path to the name field is
args.0.1(array index 0, key "1"). - Apply field mapping
PUT the schema with your field mapping. Use a
mappingsarray ofsource/targetpairs. Theexcludedarray removes entire top-level keys from the output — here we excludeargsto drop the raw Gravity Forms entry and form definition, keeping only the mapped fields and other top-level envelope keys.curl — PUT /schemas/webhook/22/trigger/gform_after_submission
curl -s -X PUT \ "https://your-wordpress-site.com/wp-json/fswa/v1/schemas/webhook/22/trigger/gform_after_submission" \ -H "Authorization: Bearer <your-token>" \ -H "Content-Type: application/json" \ -d '{ "include_user_data": false, "field_mapping": { "mappings": [ {"source": "args.0.1", "target": "name"}, {"source": "args.0.2", "target": "email"}, {"source": "args.0.3", "target": "phone"}, {"source": "args.0.4", "target": "subject"}, {"source": "args.0.5", "target": "message"}, {"source": "args.0.form_id", "target": "form_id"} ], "excluded": ["args"], "includeUnmapped": true } }' | jq .field_mapping
response — confirmed mapping
{ "mappings": [ { "source": "args.0.1", "target": "name" }, { "source": "args.0.2", "target": "email" }, { "source": "args.0.3", "target": "phone" }, { "source": "args.0.4", "target": "subject" }, { "source": "args.0.5", "target": "message" }, { "source": "args.0.form_id", "target": "form_id" } ], "excluded": ["args"], "includeUnmapped": true }
After the PUT, every future delivery arrives at n8n with name, email, phone, subject, message, and form_id — the raw args array (including the full form definition and trailing nulls) is excluded. Your n8n expressions are readable and the payload is clean.
See the full PUT /schemas API reference for all available options.
Why Your Gravity Forms Webhook
Needs Retry Support
A contact form submission is often a high-intent signal — someone typing their email and pressing send is more committed than a page view. Losing that data silently is costly.
With a bare wp_remote_post() call, any of these common scenarios causes permanent data loss:
n8n restarts during deployment. Your instance hits a memory limit and returns 500. The incoming webhook URL changes and the old one starts returning 404. A rate limit is hit during a campaign burst.
None of these are edge cases in real production environments. A queue with retries means these scenarios become recoverable failures instead of silent data loss. The event log means you can diagnose and replay — instead of guessing.
The dead-letter pattern — preserving failed events for inspection and replay — is now standard across major cloud messaging platforms. AWS SQS, Azure Service Bus, and Google Cloud Pub/Sub all implement the same concept: events that exhaust their retry budget move to a dead-letter store rather than being discarded. The same principle applies to Gravity Forms webhook delivery.
Without retries, a single n8n restart during business hours can mean lost Gravity Forms leads. With retry and replay support, the same event becomes a recoverable blip — automatically resolved within minutes.
Common questions always ask.
Don't see yours? Open an issue on GitHub or check the full reference in the API docs.