Webhooks

The generic webhook provider POSTs each lead as JSON to any URL you choose. It is the on-ramp to Zapier, Make, n8n, or your own endpoint, so you can route leads anywhere without waiting for a native integration.

Enabling it

Under Keno Forms > Settings > Providers, tick Generic webhook and fill in:

  • Endpoint URL - where the lead is sent.
  • Signing secret (optional) - used to sign the request so your receiver can verify it really came from your site.

Every lead is still stored locally first, then sent to the webhook (and any other enabled providers). A webhook outage never loses a lead.

The generic webhook provider in the Keno Forms settings
The generic webhook sits alongside the named CRMs in the Providers tab, taking an endpoint URL and an optional signing secret.

The payload

The request body is JSON with the canonical lead:

{
  "event": "lead.created",
  "sent_at": "2026-07-25T10:15:00+00:00",
  "site": "https://example.co.za/",
  "name": "Jane Dube",
  "email": "jane@acme.co.za",
  "phone": "072 000 0000",
  "business": "Acme (Pty) Ltd",
  "message": "A sentence about the project.",
  "consent": true,
  "campaign_source": "google/cpc/winter-promo",
  "page_url": "https://example.co.za/pricing",
  "source": {
    "page_url": "https://example.co.za/pricing",
    "page_path": "/pricing",
    "referrer": "https://www.google.com/",
    "utm_source": "google",
    "utm_medium": "cpc",
    "utm_campaign": "winter-promo"
  },
  "fields": { "contact_person": "Jane Dube", "email": "jane@acme.co.za" }
}

fields holds the raw form values keyed by field. The top-level keys are the normalised lead, so most tools can map straight from them.

Verifying the signature

When you set a signing secret, Keno Forms adds a header:

X-KF-Signature: sha256=<hex>

The value is the HMAC-SHA256 of the exact raw request body, keyed with your secret (the same scheme GitHub and Stripe use). Recompute it on your side over the raw body and compare with a constant-time check. In PHP:

$expected = 'sha256=' . hash_hmac( 'sha256', $rawBody, $secret );
if ( ! hash_equals( $expected, $_SERVER['HTTP_X_KF_SIGNATURE'] ?? '' ) ) {
    http_response_code( 401 );
    exit;
}

Reject anything that does not match. Return a 2xx status to acknowledge receipt; other statuses are recorded against the entry as a failed dispatch.