Agent skill
Gravity Forms Webhooks Skill
Receive and authenticate Gravity Forms webhooks from the WordPress Webhooks Add-On. Use when setting up a Gravity Forms webhook handler, adding a shared-secret Request Header such as X-Webhook-Secret to a webhook feed, or parsing the form entry payload (field IDs like "1" and "3.3", id, form_id, date_created, source_url). Gravity Forms does NOT sign webhooks: there is no signature header, HMAC or signing secret, so verify by comparing the custom header value in constant time. It has no event types; a feed fires on form submission only.
Install this skill
npx skills add hookdeck/webhook-skills --skill gravity-forms-webhooks
Gravity Forms is a WordPress forms plugin. Its Webhooks Add-On makes an HTTP request to your URL when a form is submitted. You configure it as a feed under Form Settings → Webhooks.
When to Use This Skill
- How do I receive Gravity Forms form submissions with a webhook?
- How do I verify a Gravity Forms webhook signature? (You can't. Gravity Forms doesn't sign webhooks. Authenticate with a custom Request Header instead.)
- How do I protect my Gravity Forms webhook endpoint with an
X-Webhook-Secretheader? - How do I read field values (
"1","3.3") from a Gravity Forms webhook payload? - Why does my Gravity Forms webhook arrive as form data instead of JSON?
- Why is my Gravity Forms webhook missing the entry ID? (Select Fields mode sends only the keys you map.)
There Is No Signature
The Gravity Forms docs describe no signature header, no signing secret, no timestamp and no HMAC. Don't invent an X-GF-Signature verifier: no real delivery would pass it.
The mechanism available is the feed's Request Headers setting, which sends one or more HTTP headers of your choosing with every request. The docs don't prescribe a header name; this skill uses X-Webhook-Secret by convention. You:
- Generate a random secret yourself (
openssl rand -hex 32). - In the feed, add a Request Header named
X-Webhook-Secretwith the secret as value. - On your endpoint, compare the header value with your secret in constant time. Return
401if it's missing or wrong. - Fail closed: if the secret isn't configured on your server, reject the request.
This proves the caller knows the shared secret. It does not protect the body from tampering, since nothing is signed over it, so you don't need the raw body for verification: parsing before the check is safe. Checking first still avoids parsing untrusted input.
Verification (core)
const crypto = require('crypto');
// Gravity Forms sends the Request Header you configured on the feed:
// X-Webhook-Secret: <secret>
function verifyGravityFormsSecret(headerValue, secret) {
if (!secret) return false; // fail closed when GRAVITY_FORMS_WEBHOOK_SECRET is unset
if (typeof headerValue !== 'string') return false;
// Hash both sides so timingSafeEqual always gets equal-length buffers
const a = crypto.createHash('sha256').update(headerValue).digest();
const b = crypto.createHash('sha256').update(secret).digest();
return crypto.timingSafeEqual(a, b);
}
// verifyGravityFormsSecret(req.get('x-webhook-secret'), process.env.GRAVITY_FORMS_WEBHOOK_SECRET)
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Events: Form Submission Only
Gravity Forms webhooks have no event type field. A feed runs when the form is submitted, optionally only when its Webhook Condition matches. Route on form_id instead, which is part of the entry.
The body is the entry. With Request Body → All Fields Gravity Forms sends the entire unformatted entry. Keys are field IDs ("1", and "3.3" for a sub-input such as a name's first name) plus entry properties (id, form_id, date_created, source_url, ...):
{
"id": "101",
"form_id": "3",
"date_created": "2026-10-21 10:15:30",
"source_url": "https://example.com/contact/",
"1": "Ada Lovelace",
"2": "ada@example.com",
"3.3": "Ada",
"3.6": "Lovelace"
}
With Select Fields you map your own keys to form fields, entry meta or custom values (merge tags allowed) and only those are sent. Map id and form_id to be able to deduplicate and route.
The Request Format decides the encoding: JSON (with Content-Type: application/json for POST and PUT) or FORM (urlencoded). The examples accept both.
See references/overview.md for the payload and feed settings.
Environment Variables
# The secret YOU generated. The feed must send: X-Webhook-Secret: <this value>
GRAVITY_FORMS_WEBHOOK_SECRET=generate_with_openssl_rand_hex_32
# Optional: header name, if you used something other than X-Webhook-Secret
# GRAVITY_FORMS_WEBHOOK_HEADER=X-Webhook-Secret
Local Development
npx hookdeck-cli listen 3000 gravity-forms --path /webhooks/gravity-forms
No account required. The CLI creates a guest account on first run and gives you a public URL and a web UI for inspecting requests. Use port 8000 for the FastAPI example.
Gravity Forms validates the Request URL with WordPress core checks (wp_http_validate_url), so a custom port or a redirect to a URL that fails validation aborts the feed. Use the public HTTPS URL the CLI prints.
Reference Materials
- references/overview.md - How Gravity Forms webhooks work, feed settings, entry payload
- references/setup.md - Adding the feed and the secret header in WordPress
- references/verification.md - Header authentication per framework, gotchas, debugging