---
title: "Send Gravity Forms Submissions to n8n via Webhook"
description: "Forward Gravity Forms entries to n8n with a reliable, retry-capable webhook — payload mapping, HMAC signing, error handling, and end-to-end test recipe."
url: "https://wpwebhooks.org/blog/gravity-forms-to-n8n-webhook/"
date: "2026-05-17"
---

# Send Gravity Forms Submissions to n8n via Webhook

TL;DR

-   Trigger: `gform_after_submission` (or `_{id}` for one form). Pair with n8n's **Webhook trigger node**.
-   Webhook Actions auto-attaches `X-Event-Id`, `X-Event-Timestamp`, `X-Webhook-Id` for n8n-side deduplication.
-   Always route through the queue — n8n downtime triggers automatic retry (1m, 2m, 4m, 8m…) instead of silent data loss.

/ Overview

## What is the **simplest** way to send Gravity Forms data to n8n?

Add an HTTP webhook to your form that POSTs the entry as JSON to an n8n Webhook trigger node. The Gravity Forms [official Webhooks add-on](https://docs.gravityforms.com/category/add-ons-gravity-forms/webhooks-add-on/) can do this, and so can Webhook Actions. The choice between them comes down to one question: what happens when n8n is unreachable?

The Gravity Forms Webhooks add-on fires the request inline during form submission. If n8n returns a 5xx, times out, or is down for maintenance, the submission still completes on the WordPress side but the entry never reaches n8n — and there is no record of the failure beyond a single line in the form's feed log. Production deployments need a queue with automatic retry; that is the gap Webhook Actions fills.

In both cases the n8n side is identical: a Webhook trigger node with Header Auth turned on, then the workflow does the actual work (write to a CRM, push to Slack, append to a sheet).

/ n8n Side

## Which **n8n trigger node** do you use?

The [**Webhook** node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/), configured with HTTP method POST and Response Mode set to "Immediately" with response code 200. Switch the workflow to active, copy the Production URL (not the Test URL — the test URL only works while the editor is open), and use that as the destination in your WordPress configuration.

Authentication tab: pick _Header Auth_, header name `Authorization`, value `Bearer <long-random-secret>`. On the WordPress side, add the same `Authorization: Bearer ...` as a Custom Header on the webhook in Webhook Actions. Two clicks each side, no code, no shared signing secrets to rotate. Without header auth, anyone who guesses your workflow URL can post arbitrary payloads.

Response data: return `{"received": true}` only after every required field is present in the payload. Returning 200 to a malformed payload tells the WordPress side the delivery succeeded — when really the workflow exited at step two without doing anything. Validate the payload shape in n8n and return a 400 explicitly when it fails.

/ Payload Mapping

## How do you **build the Gravity Forms webhook payload**?

Two paths. The no-code path: in **Webhook Actions → Add Webhook**, paste the n8n Production URL, pick trigger [`gform_after_submission`](https://docs.gravityforms.com/gform_after_submission/) (or `gform_after_submission_{form_id}` for a single form), submit the form once to capture an example payload, then use the **Payload Mapping** editor to rename Gravity Forms field IDs (`1.3`, `1.6`, `2`) to semantic keys (`first_name`, `last_name`, `email`). Save. The next submission dispatches with the mapped shape.

The code path is for advanced enrichment — looking up a CRM record id from post meta, signing the body, injecting computed fields. Use the `fswa_webhook_payload` filter, which runs just before dispatch and receives the mapped payload, webhook ID, trigger name, and the original pre-mapping payload (see the [plugin README on WordPress.org](https://wordpress.org/plugins/flowsystems-webhook-actions/) for the full filter list). The plugin's admin UI exposes a Field Selector with live preview so most rename / restructure work never needs PHP.

Optional enrichment via fswa\_webhook\_payload filter

```
// Inject a computed field into the outgoing payload for one webhook.
add_filter( 'fswa_webhook_payload', function( $payload, $webhook_id, $trigger, $original ) {
    if ( $trigger !== 'gform_after_submission' ) {
        return $payload;
    }

    // Pull a CRM contact id stored on the entry, fall back to lookup by email.
    $payload['crm_contact_id'] =
        my_crm_lookup_contact( $payload['email'] );

    return $payload;
}, 10, 4 );
```

Scope the filter on `$webhook_id` when multiple webhooks listen to the same trigger but need different enrichment. See the [gform\_after\_submission deep dive](/blog/gravity-forms-gform-after-submission-webhook/) for hook argument details.

/ Security

## How do you **secure the n8n webhook** (bearer token + event IDs)?

Two layers, both no code. Layer one: bearer token via the plugin's built-in Custom Headers. Open the webhook in **Webhook Actions → Edit**, add a Custom Header `Authorization` with value `Bearer <long-random-secret>`, save. In the n8n Webhook node, set Authentication to _Header Auth_ with name `Authorization` and value `Bearer <same-secret>`. Anyone who guesses your workflow URL still cannot post.

Layer two: dedup with the headers Webhook Actions attaches automatically. `X-Event-Id` is a UUID stable across retries — store the last N values in n8n (a Set node + workflow static data, or a small Redis lookup) and exit early on a repeat. This matters because the queue retries on 5xx, and the same event can arrive twice if n8n returned 200 just after a transient error.

That covers 95% of real deployments. **You only need HMAC body signing if** you do not trust the network path (running n8n on the public internet without TLS pinning, or piping through a third-party proxy that could mutate the body). In that case hook the `fswa_headers` filter, compute `hash_hmac('sha256', $body, $secret)`, inject as `X-Signature`, and verify in an n8n Function node. Bearer + TLS is enough for almost everyone else.

/ Reliability

## How do you **handle retries when n8n is offline**?

Queue the payload in WordPress; do not POST inline. Webhook Actions does this by default — every dispatch goes through a queue table with status, attempt count, and per-attempt request/response history. When n8n is unreachable, retries fire with exponential backoff (1m → 2m → 4m → 8m, capped at 1h between attempts; 5 attempts before `permanently_failed` — see the [plugin README "Smart Retry" section](https://wordpress.org/plugins/flowsystems-webhook-actions/)). An inline `wp_remote_post` in `gform_after_submission` gets one chance, no log, and the entry is gone.

On the n8n side, return a 5xx (not a 4xx) when the workflow fails for a transient reason — a downstream API is rate-limiting, a CRM is briefly down. Webhook Actions treats 4xx as _permanent_ and stops retrying; 5xx and 429 trigger the next backoff. Use 5xx for "try again," 4xx for "the payload is wrong, do not retry." See [retry vs replay](/blog/wordpress-webhook-retry-replay-system/) for the data model behind both.

/ Field Mapping

## How do you **map Gravity Forms fields** to n8n nodes?

Inside the n8n Webhook node, the payload arrives under `$json.body` (or directly under `$json` depending on response settings). Reference fields with expressions like `{{ $json.body.email }}` in downstream nodes. The semantic-key approach from the previous section means n8n expressions read like English instead of `{{ $json.body["2"] }}`.

For repeating fields (multiple checkboxes, dynamic field rows), the value is a comma-separated string in the Gravity Forms `$entry` array. Split it in the PHP mapper before sending — `explode( ',', $entry['5'] )` — so n8n receives a real array and can `Split In Batches` over it cleanly.

File uploads: Gravity Forms stores the URL in the entry array as a serialized JSON string for multi-file fields, or a plain URL for single-file. Forward the URL (n8n can fetch it with an HTTP Request node) rather than the file bytes — keeps the webhook payload small and avoids n8n timeouts on large attachments.

/ Testing

## How do you **test the flow end-to-end**?

1\. In n8n, click _Listen for test event_ on the Webhook node and submit a test entry from Gravity Forms' preview. Confirm the payload arrives and any HMAC verification passes.  
2\. Switch the n8n workflow to Active, change the WordPress webhook to the Production URL, and submit a real entry. Watch the Webhook Actions delivery log for status 200.  
3\. Simulate failure: change the WordPress webhook URL to a typo and submit again. Confirm the log shows the retry schedule and the entry lands in `permanently_failed` after the final attempt — that is your dead-letter path working.

Skip step 3 at your peril. The first time most n8n integrations fail in production is on a Saturday night when no one is watching the logs.

/ Tooling Choice

## When should you **skip the Gravity Forms Webhooks add-on** and use Webhook Actions instead?

| Concern | Gravity Forms Webhooks add-on | Webhook Actions |
| --- | --- | --- |
| Dispatch | Inline (blocks submission) | Persistent queue (Action Scheduler when available) |
| Retry on n8n 5xx | Single attempt | 5 attempts, exponential backoff up to 1h |
| Failure log | Single line per feed | Per-attempt history with request & response |
| Event identity | None | X-Event-Id, X-Event-Timestamp, X-Webhook-Id automatic |
| Replay | Manual re-submit | Replay any logged event from admin or REST |
| Conditional dispatch | Per-feed conditions | Free: 1 condition; Pro: unlimited groups w/ AND/OR |
| Bearer auth | Manual workaround | Built-in Custom Headers — zero code |
| HMAC body signing | Not built in | Add via fswa\_headers filter (5 lines) when needed |
| REST API control | None | Token-auth REST (logs, retry, replay, queue) |
| Cost | Bundled with GF Elite | Free; Pro for unlimited conditions, per-webhook retry |

The add-on is fine for low-volume forms where occasional data loss is tolerable. For lead capture, e-commerce checkouts, and anything that costs money when an entry is missed, the queue-backed approach pays for itself the first time n8n has a five-minute outage.

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"Send Gravity Forms Submissions to n8n via Webhook","description":"Forward Gravity Forms entries to n8n with a reliable, retry-capable webhook — payload mapping, HMAC signing, error handling, and end-to-end test recipe.","datePublished":"2026-05-17","dateModified":"2026-05-17","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org"},"url":"https://wpwebhooks.org/blog/gravity-forms-to-n8n-webhook/","image":"https://wpwebhooks.org/og_image.jpg","keywords":["gravity forms n8n","gravity forms webhook n8n","gform after submission n8n","gravity forms to n8n","n8n webhook trigger gravity forms"]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"WP Webhooks","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://wpwebhooks.org/blog/"},{"@type":"ListItem","position":3,"name":"Send Gravity Forms Submissions to n8n via Webhook","item":"https://wpwebhooks.org/blog/gravity-forms-to-n8n-webhook/"}]}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Can I connect Gravity Forms to n8n without writing code?","acceptedAnswer":{"@type":"Answer","text":"Yes — both the Gravity Forms Webhooks add-on (bundled with GF Elite) and Webhook Actions support a no-code configuration. The add-on fires inline during submission with one attempt and no retry. Webhook Actions queues the dispatch, retries on n8n outages with exponential backoff up to one hour, and logs every attempt with the full request and response."}},{"@type":"Question","name":"Which n8n trigger node receives Gravity Forms submissions?","acceptedAnswer":{"@type":"Answer","text":"The Webhook node. Set HTTP method to POST, copy the Production URL (not the Test URL), and paste it into your Gravity Forms webhook destination. Enable Header Auth with an X-API-Key for a first layer of access control, then verify HMAC in a Function node."}},{"@type":"Question","name":"How do I authenticate the request so n8n knows it came from my WordPress site?","acceptedAnswer":{"@type":"Answer","text":"Easiest: bearer token via the plugin's built-in Custom Headers. Add a Custom Header named Authorization with value Bearer <long-random-secret> on the webhook in Webhook Actions, then enable Header Auth on the n8n Webhook node with the same name and value. No code on either side. Use HMAC body signing only when you do not trust the transport — hook the fswa_headers filter, compute hash_hmac sha256, and verify in an n8n Function node."}},{"@type":"Question","name":"What happens to entries when n8n is offline?","acceptedAnswer":{"@type":"Answer","text":"With the Gravity Forms Webhooks add-on they are lost — the dispatch fires inline, sees the failure, logs one line in the feed, and moves on. With Webhook Actions they enter a retry queue with exponential backoff (1m, 2m, 4m, 8m, capped at 1h between attempts; 5 attempts by default). Failed entries land in the permanently_failed list, which is queryable and replayable from the admin UI or REST API."}},{"@type":"Question","name":"How do I send a specific Gravity Forms form to n8n while leaving others alone?","acceptedAnswer":{"@type":"Answer","text":"Hook gform_after_submission_FORMID instead of gform_after_submission. The suffix is the form ID. For example add_action gform_after_submission_3 fires only on form 3. Inside the callback, build the payload and enqueue the webhook for that one form."}}]}
```
