Webhooks — API reference
Registering and testing delivery endpoints.
| Method | Path | Summary |
|---|---|---|
GET | /v1/webhooks | List delivery endpoints |
POST | /v1/webhooks | Register a delivery endpoint |
GET | /v1/webhooks/{webhookId} | Read one endpoint |
PATCH | /v1/webhooks/{webhookId} | Edit an endpoint |
DELETE | /v1/webhooks/{webhookId} | Delete an endpoint |
POST | /v1/webhooks/{webhookId}/rotate-secret | Issue a new signing secret |
POST | /v1/webhooks/{webhookId}/test | Send a test delivery |
GET | /v1/webhooks/{webhookId}/deliveries | Delivery log of one endpoint |
Generated from openapi.yaml at build time. Base URL https://api.tenergy.me/v1, or https://api-nile.tenergy.me/v1 on Nile (Environments). Every request below is signed as in Authentication unless its Auth line says otherwise.
List delivery endpoints
GET /v1/webhooks · listWebhooks
Auth: API key (HMAC).
The secret is never returned here.
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | Missing, malformed or rejected credentials. |
500 | Something broke on our side. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
data | array of object (WebhookEndpoint) | yes | |
data[].id | string | yes | |
data[].url | string (uri) | yes | |
data[].role | enum: primary, backup | yes | |
data[].events | array of enum (13 values, WebhookEventType) | null | null means all events. | |
data[].is_active | boolean | yes | |
data[].last_delivery_at | string (date-time) | null | ||
data[].last_delivery_status | integer | null | ||
data[].created_at | string (date-time) | yes | |
data[].updated_at | string (date-time) | ||
max_endpoints | integer | yes | |
roles | array of enum: primary, backup |
Register a delivery endpoint
POST /v1/webhooks · createWebhook
Auth: API key (HMAC).
Returns a secret shown exactly once. It signs every delivery to this endpoint — store
it now; if you lose it, rotate rather than re-register.
At most two endpoints per account: one primary and one backup. Delivery is not fan-out
— every event goes to the primary, and moves to the backup only after the primary’s retries
are exhausted. The first endpoint you create becomes primary.
URL requirements, enforced on create and on every edit: https only; must resolve to a
public address (loopback, RFC 1918, link-local including 169.254.169.254 and other
non-routable ranges are rejected); no credentials in the URL; at most 2048 characters.
Full event catalogue, payloads, signature scheme and retry schedule: Webhooks.
Request body
JSON (WebhookRequest), required.
| Field | Type | Required | Description |
|---|---|---|---|
url | string (uri) | yes | HTTPS, publicly resolvable, no credentials. |
role | enum: primary, backup | Omit to take the first free role. | |
events | array of enum (13 values, WebhookEventType) | Which events to deliver here. Omit to receive all of them — recommended, since new event types then start arriving without a registration change. Route by the event field and ignore types you do not handle yet. |
Example request body, from the contract (illustrative values):
{
"url": "https://acme.example/hooks/tenergy",
"role": "primary",
"events": ["order.confirmed","order.failed","balance.credited"]
}
Responses
| Status | Meaning |
|---|---|
201 | Created. secret is present only in this response. |
400 | Malformed request — bad JSON, unknown field, wrong type. |
401 | Missing, malformed or rejected credentials. |
409 | The request contradicts the current state of the object. |
422 | Syntactically valid but semantically impossible. |
500 | Something broke on our side. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
url | string (uri) | yes | |
role | enum: primary, backup | yes | |
events | array of enum (13 values, WebhookEventType) | null | null means all events. | |
is_active | boolean | yes | |
last_delivery_at | string (date-time) | null | ||
last_delivery_status | integer | null | ||
created_at | string (date-time) | yes | |
updated_at | string (date-time) | ||
secret | string | yes | Signing secret. Shown once, never returned by GET. |
Read one endpoint
GET /v1/webhooks/{webhookId} · getWebhook
Auth: API key (HMAC).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
webhookId | path | string | yes |
Responses
| Status | Meaning |
|---|---|
200 | OK |
401 | Missing, malformed or rejected credentials. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
500 | Something broke on our side. |
Response fields (WebhookEndpoint)
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
url | string (uri) | yes | |
role | enum: primary, backup | yes | |
events | array of enum (13 values, WebhookEventType) | null | null means all events. | |
is_active | boolean | yes | |
last_delivery_at | string (date-time) | null | ||
last_delivery_status | integer | null | ||
created_at | string (date-time) | yes | |
updated_at | string (date-time) |
Edit an endpoint
PATCH /v1/webhooks/{webhookId} · updateWebhook
Auth: API key (HMAC).
Change url, events, is_active and/or role. Sending {"role": "primary"} to the
backup swaps the two roles in one transaction, so you are never left without a primary.
A changed URL is re-validated. is_active: false pauses delivery without losing the
endpoint; pausing the primary does not promote the backup — swap the roles if that is
what you want.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
webhookId | path | string | yes |
Request body
JSON (WebhookPatch), required.
| Field | Type | Required | Description |
|---|---|---|---|
url | string (uri) | ||
role | enum: primary, backup | ||
events | array of enum (13 values, WebhookEventType) | null | ||
is_active | boolean |
Responses
| Status | Meaning |
|---|---|
200 | Updated |
400 | Malformed request — bad JSON, unknown field, wrong type. |
401 | Missing, malformed or rejected credentials. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
422 | Syntactically valid but semantically impossible. |
500 | Something broke on our side. |
Response fields (WebhookEndpoint)
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
url | string (uri) | yes | |
role | enum: primary, backup | yes | |
events | array of enum (13 values, WebhookEventType) | null | null means all events. | |
is_active | boolean | yes | |
last_delivery_at | string (date-time) | null | ||
last_delivery_status | integer | null | ||
created_at | string (date-time) | yes | |
updated_at | string (date-time) |
Delete an endpoint
DELETE /v1/webhooks/{webhookId} · deleteWebhook
Auth: API key (HMAC).
Hard delete; frees the role. Undelivered events for this endpoint are dropped.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
webhookId | path | string | yes |
Responses
| Status | Meaning |
|---|---|
204 | Deleted. No body. |
401 | Missing, malformed or rejected credentials. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
500 | Something broke on our side. |
Issue a new signing secret
POST /v1/webhooks/{webhookId}/rotate-secret · rotateWebhookSecret
Auth: API key (HMAC).
Returns a new secret once. It takes effect immediately for subsequent deliveries; there is no overlap window, so deploy the new secret to your receiver before rotating, or accept a brief period where in-flight retries fail signature verification. Each endpoint has its own secret — rotating the primary’s does not touch the backup’s.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
webhookId | path | string | yes |
Responses
| Status | Meaning |
|---|---|
200 | Rotated |
401 | Missing, malformed or rejected credentials. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
500 | Something broke on our side. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | |
secret | string | yes |
Send a test delivery
POST /v1/webhooks/{webhookId}/test · testWebhook
Auth: API key (HMAC).
Sends a synthetic event to the endpoint and reports what your server answered. The payload
is signed exactly like a real one and carries "test": true at the top level, so a
receiver that ignores unknown event types, or that checks the flag, will not act on it.
Test deliveries are not retried and do not appear in delivery history.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
webhookId | path | string | yes |
Request body
JSON.
| Field | Type | Required | Description |
|---|---|---|---|
event | enum (13 values, WebhookEventType) | Full catalogue with payloads in Webhooks. balance.credited for a deposit also carries asset (TRX|USDT), usdt_amount, rate (SUN per USDT after spread), rate_source, spread_bps, txid, sender, block_number; the USDT fields are null for TRX. |
Responses
| Status | Meaning |
|---|---|
200 | The delivery was attempted; the result describes what happened. |
401 | Missing, malformed or rejected credentials. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
500 | Something broke on our side. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
delivered | boolean | yes | True when your endpoint answered 2xx. |
event | enum (13 values, WebhookEventType) | yes | Full catalogue with payloads in Webhooks. balance.credited for a deposit also carries asset (TRX|USDT), usdt_amount, rate (SUN per USDT after spread), rate_source, spread_bps, txid, sender, block_number; the USDT fields are null for TRX. |
delivery_id | string | yes | |
response_status | integer | null | HTTP status your endpoint returned; null on connection failure. | |
response_body_excerpt | string | null | First 512 bytes of your response, for debugging. | |
error | string | null | Transport-level failure description, when delivered is false. | |
duration_ms | integer |
Delivery log of one endpoint
GET /v1/webhooks/{webhookId}/deliveries · listWebhookDeliveries
Auth: API key (HMAC).
Every event queued for this endpoint, newest first: which event, how many attempts, the
state, the last HTTP status your server answered and when the next retry is due. Test
deliveries are not listed. Scope webhooks.read, or a dashboard session with member
role viewer. Another account’s endpoint answers 404.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
webhookId | path | string | yes | |
limit | query | integer | ||
cursor | query | string | Opaque cursor from a previous response’s next_cursor. |
Responses
| Status | Meaning |
|---|---|
200 | OK |
400 | Malformed request — bad JSON, unknown field, wrong type. |
401 | Missing, malformed or rejected credentials. |
403 | Authenticated, but this key is not allowed to do it. |
404 | No such object, or it belongs to another account. The two are not distinguished. |
500 | Something broke on our side. |
Response fields
| Field | Type | Required | Description |
|---|---|---|---|
data | array of object (WebhookDelivery) | yes | |
data[].id | string | yes | |
data[].event_id | string | yes | The id of the event envelope — your dedup key, stable across retries. |
data[].event | enum (13 values, WebhookEventType) | yes | Full catalogue with payloads in Webhooks. balance.credited for a deposit also carries asset (TRX|USDT), usdt_amount, rate (SUN per USDT after spread), rate_source, spread_bps, txid, sender, block_number; the USDT fields are null for TRX. |
data[].attempt | integer | yes | Attempts made so far. |
data[].state | enum: pending, delivering, delivered, failed, dead | yes | failed is retried at next_attempt_at; dead is not retried. |
data[].response_status | integer | null | yes | HTTP status of the last attempt; null before one, or on a connection failure. |
data[].error | string | null | yes | Transport failure of the last attempt. |
data[].next_attempt_at | string (date-time) | null | yes | |
data[].delivered_at | string (date-time) | null | yes | |
data[].created_at | string (date-time) | yes | |
data[].updated_at | string (date-time) | yes | |
next_cursor | string | null | yes |
Example 200 response, from the contract (illustrative values — live numbers come from the API):
{
"data": [
{
"id": "dlv_01K5YA1B2C3D4E5F6G7H8J9K0M",
"event_id": "evt_01K5YA1B2C3D4E5F6G7H8J9K0N",
"event": "order.confirmed",
"attempt": 2,
"state": "failed",
"response_status": 502,
"error": null,
"next_attempt_at": "2026-09-25T10:31:00.000Z",
"delivered_at": null,
"created_at": "2026-09-25T10:30:00.000Z",
"updated_at": "2026-09-25T10:30:30.000Z"
}
],
"next_cursor": null
}