Create Tenant
POST /api/v1/tenants
Creates a new tenant and its first Owner user in a single atomic operation. Automatically
generates the webhookSecret (HMAC-SHA256) used to sign outbound webhooks.
Auth: none
Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique organization name |
ownerEmail | string | Yes | Initial owner user email — unique on the platform |
ownerPassword | string | Yes | Owner password — stored as a hash |
maxTrys | integer | No (default 10) | Maximum delivery attempts before the circuit breaker opens |
circuitBreakerTimer | integer | No (default 300) | Seconds the circuit stays open before a half-open probe |
deviceFingerprint | string | No | Browser fingerprint (FingerprintJS visitorId) — cloud only, ignored self-hosted |
cfTurnstileToken | string | No | Cloudflare Turnstile token — cloud only, ignored self-hosted |
using var client = new HttpClient();
var response = await client.PostAsJsonAsync("https://api.hooksentry.com/api/v1/tenants", new
{
name = "Acme Inc",
ownerEmail = "owner@acme.com",
ownerPassword = "SenhaSegura123!",
maxTrys = 10,
circuitBreakerTimer = 300
});
Return codes
201 Created— tenant and owner created, includes the generatedwebhookSecret400 Bad Request— invalid data (malformed email, empty password)409 Conflict— a tenant with the same name already exists, or the email is already in use422 Unprocessable Entity— rejected by a registration guard, e.g. disposable email (cloud only)429 Too Many Requests— more than 5 requests from the same IP within 1 hour
{
"tenantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"tenantName": "Acme Inc",
"webhookSecret": "whsec_5f8a3b2c1d9e4f6a7b8c9d0e1f2a3b4c",
"maxTrys": 10,
"circuitBreakerTimer": 300,
"tenantCreatedAt": "2026-07-03T14:32:00.000Z",
"ownerUserId": "9c858901-8a57-4791-81fe-4c455b099bc9",
"ownerEmail": "owner@acme.com",
"ownerRole": "Owner",
"ownerCreatedAt": "2026-07-03T14:32:00.000Z"
}
Cloud-only anti-abuse fields:
deviceFingerprint and cfTurnstileToken are only enforced on HookSentry Cloud. Self-hosted
deployments accept and ignore them.
Get Tenant by ID
GET /api/v1/tenants/{id}
Looks up a tenant by UUID. Never returns webhookSecret.
Auth: Bearer token
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | UUID | Tenant UUID |
var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.hooksentry.com/api/v1/tenants/{tenantId}");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var client = new HttpClient();
var response = await client.SendAsync(request);
Return codes
200 OK— tenant data (withoutwebhookSecret)401 Unauthorized— missing or invalid token404 Not Found— tenant not found
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Acme Inc",
"maxTrys": 10,
"circuitBreakerTimer": 300,
"createdAt": "2026-07-03T14:32:00.000Z",
"updatedAt": "2026-07-03T14:32:00.000Z"
}
Update Tenant
PATCH /api/v1/tenants/{id}
Partially updates the tenant's name and webhook delivery settings. Only Admin or Owner users may call this endpoint, and only for their own tenant.
Auth: Bearer token — Admin or Owner role
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | UUID | Tenant UUID — must match the caller's tenant |
Body (all fields optional)
| Field | Type | Description |
|---|---|---|
name | string | New unique tenant name |
maxTrys | integer | Max delivery attempts before opening the circuit breaker (min 1) |
circuitBreakerTimer | integer | Seconds the circuit stays open before a half-open probe (min 1) |
var request = new HttpRequestMessage(HttpMethod.Patch, $"https://api.hooksentry.com/api/v1/tenants/{tenantId}")
{
Content = JsonContent.Create(new { maxTrys = 15 })
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var client = new HttpClient();
var response = await client.SendAsync(request);
Return codes
200 OK— tenant updated400 Bad Request— invalid value401 Unauthorized— missing or invalid token403 Forbidden— caller is not Admin/Owner, or the tenant belongs to another account404 Not Found— tenant not found409 Conflict— name already taken by another tenant
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Acme Inc",
"maxTrys": 15,
"circuitBreakerTimer": 300,
"createdAt": "2026-07-03T14:32:00.000Z",
"updatedAt": "2026-07-03T15:10:00.000Z"
}
Get Webhook Secret
GET /api/v1/tenants/{id}/webhook-secret
Returns the HMAC-SHA256 secret used to sign outbound webhooks delivered to destination URLs.
Auth: Bearer token — caller's own tenant only
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://api.hooksentry.com/api/v1/tenants/{tenantId}/webhook-secret");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var client = new HttpClient();
var response = await client.SendAsync(request);
Return codes
200 OK— webhook secret401 Unauthorized— missing or invalid token403 Forbidden— tenant belongs to another user404 Not Found— tenant not found
{
"webhookSecret": "whsec_5f8a3b2c1d9e4f6a7b8c9d0e1f2a3b4c"
}
Regenerate Webhook Secret
POST /api/v1/tenants/{id}/webhook-secret
Generates a new HMAC-SHA256 webhook secret and invalidates the previous one. All destination
servers must be updated with the new secret to keep verifying the X-HookSentry-Signature
header on incoming webhooks.
Auth: Bearer token — caller's own tenant only
var request = new HttpRequestMessage(HttpMethod.Post,
$"https://api.hooksentry.com/api/v1/tenants/{tenantId}/webhook-secret");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var client = new HttpClient();
var response = await client.SendAsync(request);
Return codes
200 OK— new webhook secret401 Unauthorized— missing or invalid token403 Forbidden— tenant belongs to another user404 Not Found— tenant not found
{
"webhookSecret": "whsec_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
}
Verify Webhook Signature
POST /api/v1/tenants/{id}/webhook-secret/verify
Computes HMAC-SHA256 of the provided payload using the tenant's webhook secret and compares it
(timing-safe) against the provided signature. Useful for testing your X-HookSentry-Signature
verification logic without waiting for a real delivery.
Auth: Bearer token — caller's own tenant only
Body
| Field | Type | Required | Description |
|---|---|---|---|
payload | string | Yes | Raw JSON string that was signed |
signature | string | Yes | Value of the X-HookSentry-Signature header, e.g. sha256=abc123... |
var request = new HttpRequestMessage(HttpMethod.Post,
$"https://api.hooksentry.com/api/v1/tenants/{tenantId}/webhook-secret/verify")
{
Content = JsonContent.Create(new { payload = rawBody, signature = headerValue })
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var client = new HttpClient();
var response = await client.SendAsync(request);
Return codes
200 OK—{ "valid": true | false }401 Unauthorized— missing or invalid token403 Forbidden— tenant belongs to another user404 Not Found— tenant not found
{
"valid": true
}