Webhooks let you receive real-time HTTP notifications when events happen in your Endors Space. When an event fires, Endors sends a POST request to your endpoint with a signed JSON payload.
Webhook configuration (creating, updating, deleting) requires a browser session and is done through the Endors dashboard under Settings > API & Webhooks . The API key can be used to read webhook configurations and delivery logs.
Setting up a webhook In Settings > API & Webhooks, click + Create Webhook and fill in:
Webhook Name — a label for your reference
Webhook Endpoint URL — a publicly accessible HTTPS URL on your server
Subscribe to Events — choose one or more events to listen for
Your webhook secret is generated automatically. It is used to verify that requests come from Endors.
Supported events Event
When it fires
submission.completed
A submitter finishes the collection form
submission.accepted
You accept a submission in the review queue
testimonial.created
A testimonial is created from an accepted submission
client.created
A new client is created (from a submission, the dashboard, or import)
embedding.created
A new embedding is created in your Space
collection.link.opened
Someone opens a personal request link
Webhook payload envelope Every delivery sends a POST request with Content-Type: application/json. The body always includes:
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:00:00.000Z" ,
"apiVersion" : "v1" ,
"data" : { }
}
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:00:00.000Z" ,
"apiVersion" : "v1" ,
"data" : { }
}
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:00:00.000Z" ,
"apiVersion" : "v1" ,
"data" : { }
}
Field
Description
id
Unique delivery UUID
event
The event name
spaceId
Your Space ID
timestamp
ISO 8601 timestamp of when the event fired
apiVersion
Always "v1"
data
Event-specific payload (see below)
Verifying the signature Every request includes two signature headers. Use either one:
X-Endors-Signature: sha256=< hmac-sha256-hex >
X-Webhook-Signature: sha256=< hmac-sha256-hex >
X-Endors-Signature: sha256=< hmac-sha256-hex >
X-Webhook-Signature: sha256=< hmac-sha256-hex >
X-Endors-Signature: sha256=< hmac-sha256-hex >
X-Webhook-Signature: sha256=< hmac-sha256-hex >
Also included:
X-Delivery-Id: < delivery-uuid >
X-Delivery-Id: < delivery-uuid >
X-Delivery-Id: < delivery-uuid >
To verify:
Get the raw request body bytes (before parsing JSON).
Compute HMAC-SHA256 of the raw body using your webhook secret.
Prefix the hex digest with sha256=.
Compare to the X-Endors-Signature header using a timing-safe comparison.
Node.js example:
const crypto = require ( "crypto" ) ;
function verifySignature ( rawBody , secret , signatureHeader ) {
const expected = "sha256=" + crypto
.createHmac ( "sha256" , secret )
.update ( rawBody )
.digest ( "hex" ) ;
return crypto .timingSafeEqual (
Buffer .from ( expected ) ,
Buffer .from ( signatureHeader )
) ;
}
const crypto = require ( "crypto" ) ;
function verifySignature ( rawBody , secret , signatureHeader ) {
const expected = "sha256=" + crypto
.createHmac ( "sha256" , secret )
.update ( rawBody )
.digest ( "hex" ) ;
return crypto .timingSafeEqual (
Buffer .from ( expected ) ,
Buffer .from ( signatureHeader )
) ;
}
const crypto = require ( "crypto" ) ;
function verifySignature ( rawBody , secret , signatureHeader ) {
const expected = "sha256=" + crypto
.createHmac ( "sha256" , secret )
.update ( rawBody )
.digest ( "hex" ) ;
return crypto .timingSafeEqual (
Buffer .from ( expected ) ,
Buffer .from ( signatureHeader )
) ;
}
Important: Always use the raw body bytes, not a re-serialized version of the parsed JSON.
Retry policy Endors retries failed deliveries automatically. Your endpoint must return a 2xx status code within 10 seconds.
Attempt
Delay before attempt
1
Immediately
2
2 seconds
3
8 seconds
4
32 seconds
5
128 seconds
If your endpoint returns a 4xx status, Endors marks the delivery as exhausted immediately with no further retries. A 5xx or network timeout triggers the retry schedule.
You can also manually retry a failed delivery from the webhook delivery logs in Settings.
Delivery status values Status
Meaning
pending
Not yet attempted
success
Delivered successfully
failed
Last attempt failed, more retries scheduled
exhausted
All retries failed or a 4xx was returned
Event payloads submission.completedFires when a submitter finishes the collection form.
{
"id" : "uuid" ,
"event" : "submission.completed" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:00:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"submissionId" : "uuid" ,
"collectionLink" : {
"id" : "uuid" ,
"shortId" : "abc123" ,
"slug" : "my-link" ,
"name" : "Homepage Form" ,
"type" : "standard"
} ,
"type" : "video" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null
} ,
"textContent" : null ,
"rating" : 5 ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"customFields" : [ ] ,
"clientId" : null ,
"submittedAt" : "2026-05-21T10:00:00.000Z"
}
}
{
"id" : "uuid" ,
"event" : "submission.completed" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:00:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"submissionId" : "uuid" ,
"collectionLink" : {
"id" : "uuid" ,
"shortId" : "abc123" ,
"slug" : "my-link" ,
"name" : "Homepage Form" ,
"type" : "standard"
} ,
"type" : "video" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null
} ,
"textContent" : null ,
"rating" : 5 ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"customFields" : [ ] ,
"clientId" : null ,
"submittedAt" : "2026-05-21T10:00:00.000Z"
}
}
{
"id" : "uuid" ,
"event" : "submission.completed" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:00:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"submissionId" : "uuid" ,
"collectionLink" : {
"id" : "uuid" ,
"shortId" : "abc123" ,
"slug" : "my-link" ,
"name" : "Homepage Form" ,
"type" : "standard"
} ,
"type" : "video" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null
} ,
"textContent" : null ,
"rating" : 5 ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"customFields" : [ ] ,
"clientId" : null ,
"submittedAt" : "2026-05-21T10:00:00.000Z"
}
}
submission.acceptedFires when you accept a submission in the review queue.
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"submissionId" : "uuid" ,
"testimonialId" : "uuid" ,
"collectionLinkId" : "uuid" ,
"type" : "video" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null
} ,
"textContent" : null ,
"rating" : 5 ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"clientId" : "uuid" ,
"submittedAt" : "2026-05-21T10:00:00.000Z" ,
"acceptedAt" : "2026-05-21T10:05:00.000Z"
}
}
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"submissionId" : "uuid" ,
"testimonialId" : "uuid" ,
"collectionLinkId" : "uuid" ,
"type" : "video" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null
} ,
"textContent" : null ,
"rating" : 5 ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"clientId" : "uuid" ,
"submittedAt" : "2026-05-21T10:00:00.000Z" ,
"acceptedAt" : "2026-05-21T10:05:00.000Z"
}
}
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"submissionId" : "uuid" ,
"testimonialId" : "uuid" ,
"collectionLinkId" : "uuid" ,
"type" : "video" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null
} ,
"textContent" : null ,
"rating" : 5 ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"clientId" : "uuid" ,
"submittedAt" : "2026-05-21T10:00:00.000Z" ,
"acceptedAt" : "2026-05-21T10:05:00.000Z"
}
}
testimonial.createdFires when a testimonial is created from an accepted submission.
{
"id" : "uuid" ,
"event" : "testimonial.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:01.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"testimonialId" : "uuid" ,
"fromSubmissionId" : "uuid" ,
"name" : "Jane Smith" ,
"type" : "video" ,
"rating" : 5 ,
"textContent" : null ,
"clientId" : "uuid" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com"
} ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"submittedAt" : "2026-05-21T10:00:00.000Z"
}
}
{
"id" : "uuid" ,
"event" : "testimonial.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:01.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"testimonialId" : "uuid" ,
"fromSubmissionId" : "uuid" ,
"name" : "Jane Smith" ,
"type" : "video" ,
"rating" : 5 ,
"textContent" : null ,
"clientId" : "uuid" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com"
} ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"submittedAt" : "2026-05-21T10:00:00.000Z"
}
}
{
"id" : "uuid" ,
"event" : "testimonial.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:01.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"testimonialId" : "uuid" ,
"fromSubmissionId" : "uuid" ,
"name" : "Jane Smith" ,
"type" : "video" ,
"rating" : 5 ,
"textContent" : null ,
"clientId" : "uuid" ,
"submitter" : {
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com"
} ,
"asset" : {
"id" : "uuid" ,
"filename" : "testimonial.mp4" ,
"mimeType" : "video/mp4" ,
"sizeBytes" : 45000000 ,
"storagePath" : null ,
"storageBucket" : null
} ,
"submittedAt" : "2026-05-21T10:00:00.000Z"
}
}
client.createdFires when a new client is created.
{
"id" : "uuid" ,
"event" : "client.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:01.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"clientId" : "uuid" ,
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null ,
"status" : "active" ,
"tags" : [ ] ,
"customFields" : [ ]
}
}
{
"id" : "uuid" ,
"event" : "client.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:01.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"clientId" : "uuid" ,
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null ,
"status" : "active" ,
"tags" : [ ] ,
"customFields" : [ ]
}
}
{
"id" : "uuid" ,
"event" : "client.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:05:01.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"clientId" : "uuid" ,
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null ,
"jobTitle" : "CEO" ,
"location" : null ,
"product" : null ,
"service" : null ,
"status" : "active" ,
"tags" : [ ] ,
"customFields" : [ ]
}
}
Only fields that are set on the client are included in the payload.
embedding.createdFires when a new embedding is created.
{
"id" : "uuid" ,
"event" : "embedding.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:10:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"embeddingId" : "uuid" ,
"name" : "Homepage Testimonials" ,
"testimonialType" : "video" ,
"testimonialIds" : [ "uuid" , "uuid" ] ,
"configOverrides" : null ,
"embedIframeSnippet" : "<iframe src=\"https://app.endors.io/embed/uuid\" ...></iframe>"
}
}
{
"id" : "uuid" ,
"event" : "embedding.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:10:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"embeddingId" : "uuid" ,
"name" : "Homepage Testimonials" ,
"testimonialType" : "video" ,
"testimonialIds" : [ "uuid" , "uuid" ] ,
"configOverrides" : null ,
"embedIframeSnippet" : "<iframe src=\"https://app.endors.io/embed/uuid\" ...></iframe>"
}
}
{
"id" : "uuid" ,
"event" : "embedding.created" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T10:10:00.000Z" ,
"apiVersion" : "v1" ,
"data" : {
"embeddingId" : "uuid" ,
"name" : "Homepage Testimonials" ,
"testimonialType" : "video" ,
"testimonialIds" : [ "uuid" , "uuid" ] ,
"configOverrides" : null ,
"embedIframeSnippet" : "<iframe src=\"https://app.endors.io/embed/uuid\" ...></iframe>"
}
}
collection.link.openedFires when someone opens a personal request link.
{
"id" : "uuid" ,
"event" : "collection.link.opened" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T11:00:00.000Z" ,
"apiVersion" : "v1" ,
"collectionLinkId" : "uuid" ,
"shortId" : "abc123" ,
"slug" : "my-link" ,
"name" : "Personal Request — Jane" ,
"status" : "active" ,
"linkType" : "personal" ,
"testimonialType" : "video" ,
"data" : {
"linkedClient" : {
"id" : "uuid" ,
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null
}
}
}
{
"id" : "uuid" ,
"event" : "collection.link.opened" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T11:00:00.000Z" ,
"apiVersion" : "v1" ,
"collectionLinkId" : "uuid" ,
"shortId" : "abc123" ,
"slug" : "my-link" ,
"name" : "Personal Request — Jane" ,
"status" : "active" ,
"linkType" : "personal" ,
"testimonialType" : "video" ,
"data" : {
"linkedClient" : {
"id" : "uuid" ,
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null
}
}
}
{
"id" : "uuid" ,
"event" : "collection.link.opened" ,
"spaceId" : "uuid" ,
"timestamp" : "2026-05-21T11:00:00.000Z" ,
"apiVersion" : "v1" ,
"collectionLinkId" : "uuid" ,
"shortId" : "abc123" ,
"slug" : "my-link" ,
"name" : "Personal Request — Jane" ,
"status" : "active" ,
"linkType" : "personal" ,
"testimonialType" : "video" ,
"data" : {
"linkedClient" : {
"id" : "uuid" ,
"firstName" : "Jane" ,
"lastName" : "Smith" ,
"email" : "jane@acme.com" ,
"phone" : null ,
"company" : "Acme Studios" ,
"website" : null
}
}
}
Note: collectionLinkId, shortId, slug, name, status, linkType, and testimonialType appear at the top level of the payload (not inside data) for this event.
Reading webhook configuration via API List webhooks curl "https://app.endors.io/api/spaces/YOUR_SPACE_ID/webhooks" \
-H "Authorization: Bearer sk_live_your_api_key_here"
curl "https://app.endors.io/api/spaces/YOUR_SPACE_ID/webhooks" \
-H "Authorization: Bearer sk_live_your_api_key_here"
curl "https://app.endors.io/api/spaces/YOUR_SPACE_ID/webhooks" \
-H "Authorization: Bearer sk_live_your_api_key_here"
{
"ok" : true ,
"data" : [
{
"id" : "uuid" ,
"name" : "Production Webhook" ,
"url" : "https://api.example.com/webhooks" ,
"events" : [ "submission.accepted" , "client.created" ] ,
"active" : true ,
"created_at" : "2026-04-01T09:00:00.000Z"
}
]
}
{
"ok" : true ,
"data" : [
{
"id" : "uuid" ,
"name" : "Production Webhook" ,
"url" : "https://api.example.com/webhooks" ,
"events" : [ "submission.accepted" , "client.created" ] ,
"active" : true ,
"created_at" : "2026-04-01T09:00:00.000Z"
}
]
}
{
"ok" : true ,
"data" : [
{
"id" : "uuid" ,
"name" : "Production Webhook" ,
"url" : "https://api.example.com/webhooks" ,
"events" : [ "submission.accepted" , "client.created" ] ,
"active" : true ,
"created_at" : "2026-04-01T09:00:00.000Z"
}
]
}
The webhook secret is never returned in any API response.
List delivery logs Returns the 20 most recent deliveries for a webhook.
curl "https://app.endors.io/api/spaces/YOUR_SPACE_ID/webhooks/WEBHOOK_ID/deliveries" \
-H "Authorization: Bearer sk_live_your_api_key_here"
curl "https://app.endors.io/api/spaces/YOUR_SPACE_ID/webhooks/WEBHOOK_ID/deliveries" \
-H "Authorization: Bearer sk_live_your_api_key_here"
curl "https://app.endors.io/api/spaces/YOUR_SPACE_ID/webhooks/WEBHOOK_ID/deliveries" \
-H "Authorization: Bearer sk_live_your_api_key_here"
{
"ok" : true ,
"data" : [
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"status" : "success" ,
"attempt_count" : 1 ,
"response_status" : 200 ,
"created_at" : "2026-05-21T10:05:00.000Z" ,
"delivered_at" : "2026-05-21T10:05:01.000Z" ,
"last_attempted_at" : "2026-05-21T10:05:01.000Z"
}
]
}
{
"ok" : true ,
"data" : [
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"status" : "success" ,
"attempt_count" : 1 ,
"response_status" : 200 ,
"created_at" : "2026-05-21T10:05:00.000Z" ,
"delivered_at" : "2026-05-21T10:05:01.000Z" ,
"last_attempted_at" : "2026-05-21T10:05:01.000Z"
}
]
}
{
"ok" : true ,
"data" : [
{
"id" : "uuid" ,
"event" : "submission.accepted" ,
"status" : "success" ,
"attempt_count" : 1 ,
"response_status" : 200 ,
"created_at" : "2026-05-21T10:05:00.000Z" ,
"delivered_at" : "2026-05-21T10:05:01.000Z" ,
"last_attempted_at" : "2026-05-21T10:05:01.000Z"
}
]
}