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.

bash
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

EventDescription
conversation.completedA visitor completed a form (chat or classic)
webhook.testA test event sent via the test endpoint

Payload

All webhook payloads follow this structure:

json
{
  "event": "conversation.completed",
  "data": { ... },
  "timestamp": 1712328896789
}
FieldTypeDescription
eventstringEvent type
dataobjectEvent-specific data (see below)
timestampnumberUnix 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.

json
{
  "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
}
FieldTypeDescription
data.conversationIdstringUnique conversation ID
data.formIdstringForm ID
data.formNamestringForm name
data.statusstringAlways "COMPLETED"
data.collectedDataRecord<string, any>All question-answer pairs
data.visitorNamestring | nullExtracted from collectedData if available
data.visitorEmailstring | nullExtracted from collectedData if available
data.completedAtstringISO 8601 timestamp

webhook.test payload

json
{
  "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

  1. Rivelko computes HMAC-SHA256(raw_json_body, your_secret) and sends the result as a hex string in the X-Rivelko-Signature header
  2. Your endpoint should compute the same HMAC and compare the result
X-Rivelko-Signature: a1b2c3d4e5f6...

Verification Example (Node.js)

javascript
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)

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

PropertyValue
MethodPOST
Content-Typeapplication/json
Timeout5 seconds
RetriesNone (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:

bash
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:

Troubleshooting

IssueCauseSolution
No webhooks receivedWebhook not activeCheck isActive is true in webhook config
No webhooks receivedEvent not selectedEnsure conversation.completed is in events list
No webhooks receivedForm not on PRO/BUSINESS planUpgrade plan to enable webhooks
Test works but real events don'tConversation not completingVerify the form flow completes all questions
502 on testEndpoint unreachable or too slowCheck URL is correct and responds in < 5 seconds
Signature mismatchParsing body as JSON before verifyingVerify against the raw body string, not parsed JSON
Signature mismatchWrong secretEnsure 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