integration guide · private beta

Content Gate (reCAPTCHA alternative)

Updated May 12, 2026

Content Gate is a privacy-first alternative to reCAPTCHA and Turnstile. It binds a short-lived browser proof-of-work challenge to the exact text sent to Siftfy for spam classification — stopping automated form spam without image puzzles, tracking cookies, or intrusive user prompts.

How it works

  1. Create a site: Register a site in Dashboard > Content Gate. New sites start in monitor mode.
  2. Store secrets: Put the one-time sf_secret_... in your backend environment variables or secret manager. Only the public sf_site_... key goes into client-side code.
  3. Embed widget: Add the immutable widget/v1.js script tag and HTML data-siftfy-* form attributes.
  4. Backend verification: Before running business logic, send the canonical form content, challenge, and proof to POST /v1/content-gate/siteverify.

Frontend widget snippet

Load the immutable widget script from https://siftfy.io/widget/v1.js and configure your form element:

javascriptHTML form setup
<script
  type="module"
  src="https://siftfy.io/widget/v1.js"
  integrity="sha384-ZBVXkcvAuiWioj6JPYij1U2LscKJTj8bDlgrB2+dWGxokmMK62k0eixPg39vQuPt"
  crossorigin="anonymous"
></script>

<form
  data-siftfy-site-key="sf_site_REPLACE_WITH_YOUR_SITE_KEY"
  data-siftfy-action="contact_submit"
  data-siftfy-fields="subject,message"
>
  <input name="subject" type="text" required>
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

Content Security Policy (CSP)

If your application enforces CSP headers, add these rules:

bashCSP directives
script-src 'self' https://siftfy.io;
connect-src 'self' https://api.siftfy.io;
worker-src 'self' blob:;

Verification API contract

Send a POST request from your application backend to https://api.siftfy.io/v1/content-gate/siteverify.

Always include a customer-generated Idempotency-Key header to safely retry requests without double-evaluating challenges.

jsonPOST /v1/content-gate/siteverify payload
{
  "challenge": "<siftfy_content_gate_challenge>",
  "proof_nonce": "<siftfy_content_gate_proof>",
  "expected_action": "contact_submit",
  "expected_origin": "https://www.example.com",
  "content": "Subject line\nMessage body"
}

Backend code examples

Node.js / JavaScript

javascript
const response = await fetch("https://api.siftfy.io/v1/content-gate/siteverify", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SIFTFY_CONTENT_GATE_SECRET}`,
    "Content-Type": "application/json",
    "Idempotency-Key": submissionId,
  },
  body: JSON.stringify(payload),
  signal: AbortSignal.timeout(3000),
});

if (!response.ok) return queueForReview("verification_unavailable");
const result = await response.json();
if (!result.challenge_valid || result.decision === "indeterminate") {
  return queueForReview("verification_incomplete");
}
if (result.mode === "enforce" && result.decision === "block") {
  return rejectSubmission();
}
return result.decision === "review" ? queueForReview("content_policy") : accept();

Python

python
import os
import requests

try:
    response = requests.post(
        "https://api.siftfy.io/v1/content-gate/siteverify",
        headers={
            "Authorization": f"Bearer {os.environ['SIFTFY_CONTENT_GATE_SECRET']}",
            "Content-Type": "application/json",
            "Idempotency-Key": submission_id,
        },
        json=payload,
        timeout=3,
    )
except requests.RequestException:
    return queue_for_review("verification_unavailable")

if response.status_code != 200:
    return queue_for_review("verification_unavailable")

result = response.json()
if not result.get("challenge_valid") or result.get("decision") == "indeterminate":
    return queue_for_review("verification_incomplete")
if result.get("mode") == "enforce" and result.get("decision") == "block":
    return reject_submission()

Go

bash
body, _ := json.Marshal(payload)
req, _ := http.NewRequestWithContext(ctx, http.MethodPost,
    "https://api.siftfy.io/v1/content-gate/siteverify", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SIFTFY_CONTENT_GATE_SECRET"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", submissionID)

resp, err := (&http.Client{Timeout: 3 * time.Second}).Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
    return queueForReview("verification_unavailable")
}
defer resp.Body.Close()

var result struct {
    ChallengeValid bool   `json:"challenge_valid"`
    Decision       string `json:"decision"`
    Mode           string `json:"mode"`
}
if json.NewDecoder(resp.Body).Decode(&result) != nil ||
    !result.ChallengeValid || result.Decision == "indeterminate" {
    return queueForReview("verification_incomplete")
}
if result.Mode == "enforce" && result.Decision == "block" {
    return rejectSubmission()
}

Modes & Content Decisions

  • Monitor mode: Evaluates proofs and spam classifiers without asking your backend to enforce blocks. Ideal during initial integration.
  • Enforce mode: Signals hard blocks when content is confirmed spam.
  • Decisions: Returns allow, review, block, or indeterminate. Handle transport errors and indeterminate as fail-safe queue or review conditions.