Reference
Receiver authentication
Send a bearer token or your own headers with every delivery, for receivers that require authentication.
Every delivery is signed (X-Webhook-Signature, see
verifying signatures) — that proves a request came
from Obsidian and was not tampered with. Some receivers also sit behind an API
gateway that refuses any request without credentials, before your code ever gets
to check the signature. For those, each webhook destination can carry:
- an optional bearer token, sent as
Authorization: Bearer <token>, and - up to 10 additional headers — for example
X-Api-Key: <key>.
Both are optional. A webhook without them is delivered exactly as before.
POST /hub/in/kyc HTTP/1.1
Host: webhook.yourbank.example
Content-Type: application/json
Authorization: Bearer <your token>
X-Api-Key: <your key>
X-Webhook-Signature: t=1760342400,v1=5f2b…
X-Webhook-Delivery: 8c1d…Note
The signature is still sent and still worth verifying. The bearer token and headers get the request through your gateway; the signature proves the body is ours and unaltered. A leaked gateway token alone cannot forge a signed delivery.
Where to set it
| Destination | Where |
|---|---|
| Tenant webhook — each of development, stage and production separately | Console → Developers → Webhooks, open the environment, Authentication (optional), then Save |
| A Connect endpoint | Console → Developers → Connect, expand the endpoint, Authentication (optional) — or the auth field of the subscriptions API |
The same applies on an on-prem installation: set it in the box's own console. These credentials belong to that installation and are never copied between installations.
When the headers are sent
On every attempt to that destination: the first attempt, each automatic retry, a console Retry or Resend, the test webhook button, and every Connect fan-out or test fire.
Credentials are bound to the URL they were saved with. They are only sent to that exact URL — a delivery aimed anywhere else (an automation rule's webhook, or an old retry still pointing at a URL you have since replaced) goes without them. Changing the URL in the console asks you to enter the token and header values again (or remove them); clearing the URL removes them.
Rules
| Bearer token | Printable ASCII, no spaces, up to 4096 characters. A pasted Bearer prefix and surrounding whitespace are removed. |
| Header names | A valid HTTP token — letters, digits and ! # $ % & ' * + - . ^ _ ~ (plus the backtick and vertical bar); no spaces or colons. Up to 100 characters, each name once. |
| Header values | Printable ASCII, no line breaks or control characters, up to 2048 characters. |
| Number of headers | Up to 10. |
| Reserved names | Host, Content-Type, Content-Length, Content-Encoding, Transfer-Encoding, Connection, Keep-Alive, Proxy-Connection, Proxy-Authorization, Proxy-Authenticate, TE, Trailer, Upgrade, Expect, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, X-Real-IP, Forwarded, and every X-Webhook-* header. These are set by HTTP or by Obsidian itself. |
Authorization | Allowed as an additional header (for example Basic …), but not together with a bearer token. Use one or the other. |
Secrets stay secret
- Token and header values are write-only. After you save, the console and
the API show only header names and a masked hint (
••••••••9f3a). To change a value, type a new one; to stop sending it, remove it. - Values are encrypted at rest.
- They are never written to the delivery log, the audit log (which records
header names and what changed — set, replaced, removed — only), or
application logs. The verification detail's Webhooks tab lists the header
names with
[redacted]values.
Connect subscriptions API
POST and PATCH on /v3/automation/subscriptions/ accept an optional auth
object; responses return only its summary.
{
"url": "https://webhook.yourbank.example/hub/in/kyc",
"events": ["session.approved", "session.declined"],
"auth": {
"bearer_token": "<token>",
"headers": [{ "name": "X-Api-Key", "value": "<key>" }]
}
}"auth": {
"bearer_token_set": true,
"bearer_token_hint": "••••••••9f3a",
"headers": [{ "name": "X-Api-Key", "value_hint": "••••••••" }]
}On PATCH, every key is optional and an absent key keeps what is saved:
| You send | Effect |
|---|---|
"bearer_token": "<token>" | Set or replace the token |
"bearer_token": null (or "") | Remove the token |
"headers": [...] | The complete list — headers you leave out are removed |
{"name": "X-Api-Key"} (no value) | Keep that header's saved value |
"headers": [] | Remove every additional header |
"auth": null | Remove all authentication |
Changing url while credentials are saved returns 400 with
"code": "reentry_required" unless the same request re-enters or removes them.
Validation errors return 400 with error (a readable message), code
(invalid_header_name, invalid_header_value, invalid_bearer_token,
duplicate_header, too_many_headers, header_value_required,
authorization_conflict, reentry_required) and field.