curl --request POST \
--url https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"url": "https://hooks.example.com/1funnel/webhooks",
"event_types": [
"sms.message.delivered.v1",
"sms.message.failed.v1"
]
}
'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://hooks.example.com/1funnel/webhooks',
event_types: ['sms.message.delivered.v1', 'sms.message.failed.v1']
})
};
fetch('https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints"
payload = {
"url": "https://hooks.example.com/1funnel/webhooks",
"event_types": ["sms.message.delivered.v1", "sms.message.failed.v1"]
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)HttpResponse<String> response = Unirest.post("https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://hooks.example.com/1funnel/webhooks\",\n \"event_types\": [\n \"sms.message.delivered.v1\",\n \"sms.message.failed.v1\"\n ]\n}")
.asString();{
"id": "018f3c4e-7b2a-7c3d-8e9f-eeeeeeeeeeee",
"business_id": "018f3c4e-7b2a-7c3d-8e9f-0123456789ab",
"url": "https://hooks.example.com/1funnel/webhooks",
"description": "Production SMS delivery callbacks",
"status": "active",
"event_types": [
"sms.message.delivered.v1",
"sms.message.failed.v1"
],
"created_at": "2026-10-13T09:00:00.000Z",
"updated_at": "2026-10-13T09:00:00.000Z",
"signing_secret": "whsec_example_not_a_real_secret_0123456789abcdef"
}Create a webhook endpoint
Create an HTTPS destination with explicit event subscriptions (DEC-WH-C01).
Returns 201 Created with Location and one-time signing_secret
(DEC-WH-R01). Endpoint becomes active after successful destination
security validation. Ownership verification is deferred in v1
(DEC-WH-V01) — not required for activation.
Mandatory controls: HTTPS-only, valid TLS, server-side URL validation, SSRF / DNS-rebinding / redirect protection, prohibited private/internal destinations, endpoint-specific HMAC signing, create/update rate limiting. Maximum five active endpoints per business.
Delivery signing (DEC-WH-S01): HMAC-SHA256 over
timestamp + "." + raw_request_body; headers 1Funnel-Webhook-Id,
1Funnel-Webhook-Timestamp, 1Funnel-Webhook-Signature: v1=<lowercase_hex>.
Requires Idempotency-Key.
curl --request POST \
--url https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"url": "https://hooks.example.com/1funnel/webhooks",
"event_types": [
"sms.message.delivered.v1",
"sms.message.failed.v1"
]
}
'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://hooks.example.com/1funnel/webhooks',
event_types: ['sms.message.delivered.v1', 'sms.message.failed.v1']
})
};
fetch('https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints"
payload = {
"url": "https://hooks.example.com/1funnel/webhooks",
"event_types": ["sms.message.delivered.v1", "sms.message.failed.v1"]
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)HttpResponse<String> response = Unirest.post("https://api.1-funnel.org/v1/businesses/{businessId}/webhook-endpoints")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://hooks.example.com/1funnel/webhooks\",\n \"event_types\": [\n \"sms.message.delivered.v1\",\n \"sms.message.failed.v1\"\n ]\n}")
.asString();{
"id": "018f3c4e-7b2a-7c3d-8e9f-eeeeeeeeeeee",
"business_id": "018f3c4e-7b2a-7c3d-8e9f-0123456789ab",
"url": "https://hooks.example.com/1funnel/webhooks",
"description": "Production SMS delivery callbacks",
"status": "active",
"event_types": [
"sms.message.delivered.v1",
"sms.message.failed.v1"
],
"created_at": "2026-10-13T09:00:00.000Z",
"updated_at": "2026-10-13T09:00:00.000Z",
"signing_secret": "whsec_example_not_a_real_secret_0123456789abcdef"
}Authorizations
OAuth 2.0 Client Credentials for the External Integration API (ADR-015, ADR-023).
Client authentication: HTTP Basic (client_secret_basic).
Access-token audience: 1funnel-external-api.
The tokenUrl below is the production target. Sandbox uses a different
host. OpenAPI cannot bind tokenUrl to the selected servers entry; see
x-1funnel-oauth on the root document and the authentication guide when published.
Coarse OAuth scopes (if any) are token-level vocabulary (ADR-013 IA-06). They
are not the ADR-022 resource permission catalogue. Resource permissions
are enforced after token validation via application-to-business grants
(ADR-016) and are documented per operation with x-required-permissions.
These URLs are architecture targets. They are not claimed to be deployed or generally available.
Headers
Required for this designated state-changing operation (STD-API-01 D09, ADR-017 SECOP-05/06, DEC-SMS-I01 shared external idempotency model).
Scope: (application_id, business_id, operation, idempotency_key).
Permanent uniqueness within scope; seven-day response replay for equivalent
payloads; 409 idempotency_conflict on payload mismatch; reuse after
replay expiry must never create a new operation. See the optional
Idempotency-Key component description for concurrent and
failed-before-acceptance rules.
1 - 256"idem_01HZX3K9EXAMPLE"
Request correlation identifier (STD-API-01 D12). Clients may supply a value
of 1–128 characters matching A-Za-z0-9._-. Missing or invalid values are
replaced by a server-generated identifier. Distinct from W3C traceparent,
resource ids, Idempotency-Key, and webhook event ids.
1 - 128^[A-Za-z0-9._-]+$"req_01HZX3K9EXAMPLE"
Path Parameters
Business resource selector in /v1/businesses/{businessId}/… (ADR-006).
UUIDv7-compatible opaque identifier. {businessId} is not authorization
proof. The resource server must independently verify an active
application-to-business grant, required permission, and ownership (ADR-016,
ADR-022).
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
Body
Create a business-scoped webhook endpoint (ADR-021 WH-01/WH-06). Default
maximum five active endpoints per business (platform-configurable; not
customer-configurable in v1). Requires Idempotency-Key.
HTTPS destination URL (ADR-021 WH-05 / ADR-017 SECOP-09). Backend enforces SSRF controls at connection time (no loopback, link-local, private/internal ranges, unsafe redirects, DNS rebinding). Client-side pattern checks are not sufficient.
12 - 2048^https://"https://hooks.example.com/1funnel/webhooks"
Explicit event subscriptions for this endpoint.
1Approved v1 outbound event types (ADR-021 WH-02). Wallet and payment events are deferred. Do not rename these values.
sms.message.sent.v1, sms.message.delivered.v1, sms.message.failed.v1, sms.bulk-message.processing.v1, sms.bulk-message.completed.v1, sms.bulk-message.failed.v1, otp.challenge.verified.v1, otp.challenge.expired.v1, otp.challenge.locked.v1 256Response
Endpoint created; signing secret returned once
Create response distinguishing the ordinary endpoint representation from the
one-time signing_secret disclosure (DEC-WH-R01 / DEC-WH-C01). Clients must
store the secret securely; it cannot be retrieved later via GET/LIST.
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
HTTPS destination URL (ADR-021 WH-05 / ADR-017 SECOP-09). Backend enforces SSRF controls at connection time (no loopback, link-local, private/internal ranges, unsafe redirects, DNS rebinding). Client-side pattern checks are not sufficient.
12 - 2048^https://"https://hooks.example.com/1funnel/webhooks"
active after successful destination security validation. Ownership
verification is not required in v1.
active, disabled "active"
Approved v1 outbound event types (ADR-021 WH-02). Wallet and payment events are deferred. Do not rename these values.
sms.message.sent.v1, sms.message.delivered.v1, sms.message.failed.v1, sms.bulk-message.processing.v1, sms.bulk-message.completed.v1, sms.bulk-message.failed.v1, otp.challenge.verified.v1, otp.challenge.expired.v1, otp.challenge.locked.v1 RFC 3339 date-time. Response timestamps are UTC with a Z offset
(STD-API-01 D06). Client-supplied timestamps must include an explicit offset;
local times without an offset are rejected.
"2026-10-13T09:00:00.000Z"
RFC 3339 date-time. Response timestamps are UTC with a Z offset
(STD-API-01 D06). Client-supplied timestamps must include an explicit offset;
local times without an offset are rejected.
"2026-10-13T09:00:00.000Z"
Cryptographically secure endpoint-specific signing secret, revealed once. Never returned from GET/LIST. Never present in events or delivery inspection. Public rotation API is not in v1; internal rotation is an implementation dependency.
32 - 256"whsec_example_not_a_real_secret_0123456789abcdef"