Skip to main content
Requires a plan with the webhooks_enabled feature. Configured per-form from that form’s Webhooks settings — there’s no single workspace-wide webhook that fires for every form.
Unlike Slack, Discord, or Telegram — which get a formatted, human-readable message — a plain webhook forwards Raykoi’s full raw event envelope, untouched. Use this one if you’re parsing the payload programmatically: a backend endpoint, an n8n/Zapier-style workflow, anything that needs the structured data rather than a chat message.
Integrations page in the Raykoi dashboard, showing Notification Channels and Developer Webhooks

Setting one up

1

Add a webhook

From the form’s Webhooks settings, choose Custom, and enter any HTTPS URL you control.
2

Set a signing secret (recommended)

Optional, but strongly recommended — generated automatically if you don’t supply your own. Used to verify a delivery genuinely came from Raykoi, not an attacker who guessed your endpoint URL.
3

Save, then send a test event

Every webhook has a Send test event button — fires a real delivery with sample data through the exact same code path a live submission would use, so you can confirm your endpoint and signature verification work before going live.

The event

Currently one event type: submission.created, fired after a submission is successfully stored (never for a blocked or rate-limited attempt).
delivery_id is stable across retries of the same delivery attempt — use it to deduplicate on your end, since a retried delivery after a timeout can occasionally result in your endpoint receiving the same event twice (see Retries below).

Headers

string
Sent on every request, signed or not — the one reliable dedupe key.
string
The event type, e.g. submission.created. Only sent when the webhook has signing configured.
string
Unix timestamp (seconds) the request was signed at. Only sent when signing is configured.
string
sha256=<hex> — HMAC-SHA256 over ${timestamp}.${rawBody}, using the secret shown to you once at creation. Only sent when signing is configured.

Verifying the signature

Recompute the HMAC over the exact timestamp and raw body you received, and compare with a timing-safe function — never a plain ===, which leaks timing information an attacker can use to guess the correct signature byte-by-byte.
Binding the timestamp into the signature (not just the body) blocks replay of a captured, valid request — reject anything with a timestamp too far in the past on your end.
Verify against the raw request body, not a re-parsed/re-serialized one — JSON re-serialization can reorder keys or change whitespace, producing a different signature than what Raykoi actually signed. See Troubleshooting for the exact framework gotcha this causes.

Retries

A failed delivery (non-2xx response, timeout, or connection error) retries with exponential backoff — up to 6 attempts total, spaced 1 minute → 5 minutes → 30 minutes → 2 hours → 6 hours apart. After the 6th attempt fails, the delivery is abandoned rather than retried forever. A webhook that fails repeatedly is automatically disabled, and the workspace owner is emailed — a permanently-broken endpoint doesn’t silently accumulate failed deliveries indefinitely. Re-enable it from the form’s Webhooks settings once you’ve fixed the receiving end. This retry/disable behavior is identical for Slack, Discord, and Telegram — it’s the same delivery pipeline underneath, just a different destination.