Guides

Server webhooks

Bearer-token authentication for server-to-server lead submission — Surface 1a.

Server-to-server submission with a long-lived Bearer token. This is the surface to reach for first: your backend, or any tool that lets you set a custom header (Zapier, Make, most webhook-relay UIs), can be sending leads in a few minutes.

Endpoint

POST https://app.blackglassleads.com/api/v1/ingest

Create a server token in Settings → Ingest tokens, then send it in the Authorization header:

Authorization: Bearer ingt_srv_XXXXXXXXXXXXXXXXXXXXXXXX

Payload

The body is arbitrary JSON — there's no fixed schema, and every key becomes a field on the lead. Keep keys flat and top-level (email, name, company) rather than nested objects: your form's field configuration and the lead table both address fields by their top-level key, so a flat payload is what shows up cleanly as columns. Nested values are still accepted and stored, just not surfaced the same way.

curl -X POST "https://app.blackglassleads.com/api/v1/ingest" \
  -H "Authorization: Bearer ingt_srv_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{"email":"sarah@acme.com","name":"Sarah Chen","company":"Acme"}'

Response contract

A successful submission returns 202 Accepted:

{ "submission_id": "a1b2c3d4-e5f6-4789-9abc-def012345678" }

Every failure — missing or invalid token, wrong token type, a suspended workspace, a rate-limit trip, a malformed idempotency key, an oversized body — returns an indistinguishable 200 OK:

{ "status": "rejected" }
This is intentional, not a bug. If an invalid token got a 401 and a rate-limited valid token got a 429, the response code itself would tell an attacker whether a guessed token exists. Making every rejection reason look identical on the wire closes that side channel — the tradeoff is that you can't tell from the response alone why a submission was rejected either. To find out, open Settings → Ingest tokens → Diagnostics, which shows the real, discriminated reason for every rejection against your workspace.

Idempotency

Include an X-BGL-Idempotency-Key header (any string, 1–255 characters) to make retries safe. A repeat request with the same key from the same workspace within 24 hours returns the original response verbatim instead of creating a second lead.

cURL
curl -X POST "https://app.blackglassleads.com/api/v1/ingest" \
  -H "Authorization: Bearer ingt_srv_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "X-BGL-Idempotency-Key: order-12345" \
  -H "Content-Type: application/json" \
  -d '{"email":"sarah@acme.com"}'

A malformed idempotency key (empty, or over 255 characters) is itself an opaque-reject condition — check the Diagnostics view if retries aren't behaving as expected.

Rate limits

Per workspace, across every server token you've issued:

  • 1,000 submissions / minute
  • 50,000 submissions / day

Going over either limit returns the same opaque 200 { "status": "rejected" } — there's no 429, for the same anti-enumeration reason as above. The Diagnostics view distinguishes rate-limit rejections from every other rejection reason.

See Headers, errors & limits for the full picture across both server-side surfaces.