Webhook Guide
Como recibir datos de formularios via webhooks / How to receive form data via webhooks
Webhooks let you receive real-time HTTP notifications when events happen in your Rivelko forms. When a visitor completes a form, Rivelko sends a POST request to your endpoint with the collected data.
Los webhooks te permiten recibir notificaciones HTTP en tiempo real cuando ocurren eventos en tus formularios.
Plan requirement: Webhooks are available on PRO and BUSINESS plans only.
Setup
Webhooks are configured via the API. There is no webhook UI in the form editor yet — a dashboard configuration screen is planned.
curl -X PUT https://app.rivelko.com/api/forms/YOUR_FORM_ID/webhook \
-H "Content-Type: application/json" \
-H "Authorization: Bearer rvk_your_api_key" \
-d '{
"url": "https://myapp.com/webhooks/rivelko",
"events": ["conversation.completed"],
"isActive": true,
"secret": "whsec_my_secret_key"
}'
You can authenticate with an API key (Authorization: Bearer rvk_...) or a session cookie. Configuring a webhook requires the PRO or BUSINESS plan; on the FREE plan the endpoint returns 403 with code: "FEATURE_NOT_AVAILABLE".
See API Reference > Webhooks for full endpoint details.
Events
| Event | Description |
|---|---|
conversation.completed | A visitor completed a form (chat or classic) |
webhook.test | A test event sent via the test endpoint |
Payload
All webhook payloads follow this structure:
{
"event": "conversation.completed",
"data": { ... },
"timestamp": 1712328896789
}
| Field | Type | Description |
|---|---|---|
event | string | Event type |
data | object | Event-specific data (see below) |
timestamp | number | Unix epoch in milliseconds |
conversation.completed payload
Fired when a visitor completes all questions (conversational) or submits a classic form.
Se dispara cuando un visitante completa todas las preguntas o envia un formulario clasico.
{
"event": "conversation.completed",
"data": {
"conversationId": "clx1234...",
"formId": "clx5678...",
"formName": "Dental Clinic Contact",
"status": "COMPLETED",
"collectedData": {
"What is your name?": "Juan Perez",
"What is your email?": "juan@example.com",
"What is your phone?": "+34 912 345 678",
"What service are you interested in?": "Orthodontics"
},
"visitorName": "Juan Perez",
"visitorEmail": "juan@example.com",
"completedAt": "2026-04-05T12:34:56.789Z"
},
"timestamp": 1712328896789
}
| Field | Type | Description |
|---|---|---|
data.conversationId | string | Unique conversation ID |
data.formId | string | Form ID |
data.formName | string | Form name |
data.status | string | Always "COMPLETED" |
data.collectedData | Record<string, any> | All question-answer pairs |
data.visitorName | string | null | Extracted from collectedData if available |
data.visitorEmail | string | null | Extracted from collectedData if available |
data.completedAt | string | ISO 8601 timestamp |
webhook.test payload
{
"event": "webhook.test",
"data": {
"formId": "clx5678...",
"formName": "Dental Clinic Contact",
"message": "This is a test webhook from Rivelko."
},
"timestamp": 1712328896789
}
Signature Verification
If you set a secret when configuring your webhook, every request will include a signature header that you should verify to ensure the request is authentic.
Si configuras un secret, cada request incluira una firma que debes verificar para asegurar autenticidad.
How it works
- Rivelko computes
HMAC-SHA256(raw_json_body, your_secret)and sends the result as a hex string in theX-Rivelko-Signatureheader - Your endpoint should compute the same HMAC and compare the result
Header
X-Rivelko-Signature: a1b2c3d4e5f6...
Verification Example (Node.js)
const crypto = require("crypto");
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
}
// Express example
app.post("/webhooks/rivelko", express.text({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-rivelko-signature"];
const rawBody = req.body; // must be the raw string, not parsed JSON
if (signature && !verifyWebhookSignature(rawBody, signature, process.env.RIVELKO_WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const payload = JSON.parse(rawBody);
switch (payload.event) {
case "conversation.completed":
console.log("New submission:", payload.data.collectedData);
// Save to your database, send to CRM, trigger automation, etc.
break;
case "webhook.test":
console.log("Test webhook received");
break;
}
res.status(200).send("OK");
});
Verification Example (Python)
import hmac
import hashlib
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
@app.route("/webhooks/rivelko", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Rivelko-Signature", "")
raw_body = request.get_data()
if signature and not verify_signature(raw_body, signature, WEBHOOK_SECRET):
return "Invalid signature", 401
payload = request.get_json()
if payload["event"] == "conversation.completed":
collected = payload["data"]["collectedData"]
# Process the submission...
return "OK", 200
Delivery Behavior
| Property | Value |
|---|---|
| Method | POST |
| Content-Type | application/json |
| Timeout | 5 seconds |
| Retries | None (fire-and-forget) |
Your endpoint must respond with a 2xx status code within 5 seconds. If it times out or returns an error, the webhook is not retried.
Tu endpoint debe responder con un status 2xx en menos de 5 segundos. No hay reintentos automaticos.
Testing
Test Endpoint
Send a webhook.test event to your configured endpoint at any time:
curl -X POST https://app.rivelko.com/api/forms/YOUR_FORM_ID/webhook/test \
-H "Authorization: Bearer rvk_your_api_key"
A webhook must already be configured for the form, and the account must be on a plan that allows webhooks (PRO/BUSINESS).
Development Tools
For local development, use a tunnel service to expose your local server:
- ngrok --
ngrok http 3000 - webhook.site -- Instant webhook testing, no setup needed
- RequestBin -- Inspect incoming requests
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| No webhooks received | Webhook not active | Check isActive is true in webhook config |
| No webhooks received | Event not selected | Ensure conversation.completed is in events list |
| No webhooks received | Form not on PRO/BUSINESS plan | Upgrade plan to enable webhooks |
| Test works but real events don't | Conversation not completing | Verify the form flow completes all questions |
| 502 on test | Endpoint unreachable or too slow | Check URL is correct and responds in < 5 seconds |
| Signature mismatch | Parsing body as JSON before verifying | Verify against the raw body string, not parsed JSON |
| Signature mismatch | Wrong secret | Ensure the secret matches what's configured |
Common Use Cases
- CRM integration -- Push leads to HubSpot, Salesforce, Pipedrive
- Email automation -- Trigger welcome emails via SendGrid, Mailchimp
- Slack notifications -- Post new submissions to a Slack channel
- Database sync -- Store submissions in your own database
- Zapier / Make -- Use a webhook trigger to connect to 5000+ apps