Webhooks
Forward form submissions to your own infrastructure in real time. RizzForms POSTs JSON to your URL every time a new submission arrives.
Setup
Add a webhook via the dashboard or the API:
POST https://www.rizzness.com/api/forms/:form_id/plugins
Authorization: Bearer frk_your_api_key
Content-Type: application/json
{
"plugin_type": "webhook",
"config": {
"url": "https://your-app.com/webhooks/rizzforms"
}
}
The response includes a signing_secret — save it
immediately. This is the only time the secret is returned in
plaintext.
Payload format
Every webhook delivery sends a JSON body with this shape:
{
"id": 12345,
"created_at": "2026-03-22T21:13:49Z",
"form_id": "cBirQjTv",
"form_name": "Contact Form",
"ip": "203.0.113.42",
"user_agent": "Mozilla/5.0...",
"referrer": "https://example.com/contact",
"data": {
"email": "[email protected]",
"message": "Hello world"
}
}
| Field | Description |
|---|---|
id |
Unique submission ID. |
created_at |
ISO 8601 timestamp. |
form_id |
The form's endpoint token. |
form_name |
The form's display name. |
ip |
Submitter's IP address. |
user_agent |
Submitter's User-Agent string. |
referrer |
Page the form was submitted from. |
data |
All submitted fields (from payload_json). |
HTTP details
-
Method:
POST -
Content-Type:
application/json - Connect timeout: 3 seconds
- Read timeout: 5 seconds
If your server does not respond within these timeouts, the delivery is marked as failed and retried.
Webhook signing
Every webhook request includes an
X-RizzForms-Signature header containing an HMAC-SHA256
hex digest of the request body, signed with your webhook's signing
secret.
Verify the signature to ensure the request came from RizzForms and was not tampered with:
Ruby
expected = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, request.body.read)
if Rack::Utils.secure_compare(expected, request.headers["X-RizzForms-Signature"])
# Valid
end
Node.js
const crypto = require("crypto");
const expected = crypto
.createHmac("sha256", signingSecret)
.update(rawBody)
.digest("hex");
if (crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))) {
// Valid
}
Python
import hmac
import hashlib
expected = hmac.new(
signing_secret.encode(),
request.data,
hashlib.sha256
).hexdigest()
if hmac.compare_digest(expected, request.headers["X-RizzForms-Signature"]):
# Valid
Always use a constant-time comparison function to prevent timing attacks.
Retries
Failed deliveries are retried up to 3 times with exponential backoff. A delivery is considered failed if:
- Your server returns a non-2xx status code.
- The connection times out.
- A network error occurs.
Response bodies from your server are stored (truncated at 4 KB) for debugging. You can view delivery status and replay any submission from the dashboard.
Security
-
HTTPS required. Webhook URLs must use
https://. HTTP URLs are rejected. -
SSRF protection. Private and reserved IP addresses
(e.g.,
127.0.0.1,10.x.x.x,192.168.x.x) are blocked. Webhooks can only reach public internet endpoints.
Rotate signing secret
If your signing secret is compromised, generate a new one immediately:
POST https://www.rizzness.com/api/forms/:form_id/plugins/:id/rotate_secret
Authorization: Bearer frk_your_api_key
The response returns the new signing secret. Update your server's verification code with the new value. The old secret stops working immediately.