Webhooks API

🔔

Webhooks API

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:

  1. Get the raw request body bytes (before parsing JSON).

  2. Compute HMAC-SHA256 of the raw body using your webhook secret.

  3. Prefix the hex digest with sha256=.

  4. 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.completed

Fires 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.accepted

Fires 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.created

Fires 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.created

Fires 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.created

Fires 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.opened

Fires 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"
    }
  ]
}