#Public API — Webhook Endpoints
TheVibeCRM exposes public webhook endpoints that external systems can call to create and manage leads. These endpoints do not require user session authentication — they use per-listener secrets instead.
#Authentication
All webhook endpoints use a secret-based authentication model:
| Header | Description |
|---|---|
x-webhook-secret | A unique secret generated per listener. Must be included in every request. |
Treat your webhook secret like a password. Do not share it publicly or commit it to version control. If compromised, delete the listener and create a new one to get a fresh secret.
#Endpoint: Create Lead from External Data
/api/webhooks/supabase/{listenerId}Creates a new lead from external data using the field mappings configured for this listener.
{
"type": "INSERT",
"table": "contacts",
"schema": "public",
"record": {
"full_name": "Jane Doe",
"email": "jane@example.com",
"phone": "+1234567890",
"company": "Acme Inc"
},
"old_record": null
}{
"ok": true,
"leadId": "clxx1234567890"
}Headers:
| Header | Required | Value |
|---|---|---|
Content-Type | Yes | application/json |
x-webhook-secret | Yes | The listener's webhook secret |
Flat record format (also supported):
{
"full_name": "Jane Doe",
"email": "jane@example.com",
"phone": "+1234567890",
"company": "Acme Inc"
}The column names in the payload must match the field mappings configured in your listener. The listener translates them to lead fields (e.g. full_name → name, company → companyName).
Responses
- 201 — Lead created successfully:
{ "ok": true, "leadId": "clxx..." } - 200 — Event skipped (non-INSERT):
{ "ok": true, "skipped": true, "reason": "..." } - 401 — Invalid secret:
{ "error": "Invalid webhook secret" } - 404 — Listener not found:
{ "error": "Listener not found" } - 400 — Listener inactive or bad request:
{ "error": "Listener is inactive" } - 422 — Validation error:
{ "ok": false, "error": "Missing required fields: name and email must be mapped" }
#Endpoint: Process Stripe Subscription Event
/api/webhooks/stripe-integration/{listenerId}Processes Stripe subscription events to create or update leads.
{
"id": "evt_1234567890",
"type": "customer.subscription.created",
"data": {
"object": {
"id": "sub_1234567890",
"customer": "cus_1234567890",
"status": "active"
}
}
}{
"received": true,
"matched": 1,
"created": 0
}Headers: Content-Type: application/json, x-webhook-secret (required).
Supported Event Types
| Stripe Event | Listener Event Type |
|---|---|
customer.subscription.created | subscription_created |
customer.subscription.updated | subscription_updated |
customer.subscription.deleted | subscription_canceled |
Events that don't match the listener's configured event type are silently skipped. matched = existing leads updated; created = new leads created.
Responses
- 200 — Processed:
{ "received": true, "matched": 1, "created": 0 }or skipped:{ "received": true, "skipped": true } - 401 — Invalid secret
- 404 — Listener not found or inactive
#Endpoint: Google Calendar Push Notification
/api/webhooks/google-calendarThis endpoint is called automatically by Google when calendar events change. It is not intended to be called manually.
Google includes: x-goog-channel-id (identifies the watch), x-goog-resource-state (sync, exists, not_exists). TheVibeCRM verifies the channel ID against active watches.
#Using Webhooks with Custom Systems
You can use the Supabase webhook endpoint to send leads from any external system — not just Supabase. Send a POST with JSON body and the x-webhook-secret header; payload fields must match your configured field mappings.
cURL
curl -X POST "https://app.thevibecrm.com/api/webhooks/supabase/{listenerId}" \
-H "Content-Type: application/json" \
-H "x-webhook-secret: your-listener-secret" \
-d '{
"full_name": "John Smith",
"email": "john@company.com",
"phone": "+1987654321",
"company": "Tech Corp",
"website": "https://techcorp.com"
}'JavaScript (fetch)
const response = await fetch(
"https://app.thevibecrm.com/api/webhooks/supabase/{listenerId}",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-webhook-secret": "your-listener-secret",
},
body: JSON.stringify({
full_name: "John Smith",
email: "john@company.com",
phone: "+1987654321",
company: "Tech Corp",
}),
}
);
const data = await response.json();
console.log(data); // { ok: true, leadId: "clxx..." }Python (requests)
import requests
response = requests.post(
"https://app.thevibecrm.com/api/webhooks/supabase/{listenerId}",
headers={
"Content-Type": "application/json",
"x-webhook-secret": "your-listener-secret",
},
json={
"full_name": "John Smith",
"email": "john@company.com",
"phone": "+1987654321",
"company": "Tech Corp",
},
)
data = response.json()
print(data) # {"ok": True, "leadId": "clxx..."}