Webhooks
Create a webhook endpoint
https://ecdsa.com/api/v1/webhooksCreates a webhook endpoint for the API key's account. Creating one with a URL the account already has updates its description and event filter and switches it back on if it was switched off. The URL must be https on port 443 with a public host name; the host is resolved and refused when any of its addresses is private or reserved (webhook_url_refused). The number of endpoints is limited by the account's plan (webhook_limit_reached). Deliveries are POST requests with a JSON body, signed with ECDSA P-256 (ES256): headers ECDSA-Webhook-Id, ECDSA-Webhook-Timestamp and ECDSA-Signature (t=<unix seconds>,kid=<key id>,es256=<base64url r‖s>) over the UTF-8 bytes of "<id>.<t>.<raw body>". The public keys are the JWKS at https://ecdsa.com/.well-known/ecdsa-webhook-keys.json; every official SDK verifies deliveries with verifyWebhook().
- API 密钥
- 必需,且需要 webhooks 范围
- 使用密钥时的配额消耗
- 每次调用 1 个单位
- 速率限制
- 每个 IP 每分钟 60 次请求
请求
JSON 请求体:CreateWebhookRequest
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| url | string | 是 | Where deliveries are POSTed: an https URL on port 443 with a public host name, at most 2048 characters. |
| description | string | 否 | A note for yourself, at most 200 characters.
|
| events | string[] | 否 | Event types to receive ("monitor.grade_changed") or whole families ("monitor.*"); absent or empty receives every event. At most 20 entries.
|
响应
200The endpoint, created or updated.
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| endpoint | WebhookEndpoint | 是 | |
| created | boolean | 是 | false when the account already had an endpoint with this URL and it was updated. |
响应头: X-Request-Id · X-RateLimit-Limit · X-RateLimit-Remaining · X-RateLimit-Reset · X-Quota-Limit · X-Quota-Remaining · X-Quota-Reset
| 状态码 | 含义 | 错误码 |
|---|---|---|
| 400 | Bad request. Input limits: a request body of at most 100 KB (a larger one is answered with 413) and arrays and objects nested at most 64 levels deep, JSON documents inside string fields included. | |
| 401 | Unauthorized. | |
| 403 | Forbidden. | |
| 404 | Not found. | |
| 405 | Method not allowed. | |
| 413 | Payload too large. | |
| 429 | Too many requests. | |
| 500 | Internal server error. | |
| 503 | Service unavailable. | |
| 504 | Gateway timeout. |
每个错误响应都带有 JSON 信封;错误表说明了每个错误码的含义和处理方法。
示例
Receive every monitoring event
curl -sS 'https://ecdsa.com/api/v1/webhooks' \
-H "authorization: Bearer $ECDSA_API_KEY" \
-H 'content-type: application/json' \
-d '{
"url": "https://hooks.example.com/ecdsa",
"description": "Production alerts",
"events": [
"monitor.*"
]
}'const response = await fetch("https://ecdsa.com/api/v1/webhooks", {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${process.env.ECDSA_API_KEY}` },
body: JSON.stringify({
"url": "https://hooks.example.com/ecdsa",
"description": "Production alerts",
"events": [
"monitor.*"
]
}),
});
console.log(response.status, await response.json());import os
import httpx
response = httpx.post(
"https://ecdsa.com/api/v1/webhooks",
headers={"authorization": f"Bearer {os.environ['ECDSA_API_KEY']}"},
json={
"url": "https://hooks.example.com/ecdsa",
"description": "Production alerts",
"events": [
"monitor.*",
],
},
timeout=30.0,
)
print(response.status_code, response.json()){
"endpoint": {
"id": "7349e17c-8b41-4fc1-b1a9-dca88578ca1c",
"url": "https://hooks.example.com/ecdsa",
"description": "Production alerts",
"events": [
"monitor.*"
],
"enabled": true,
"createdAt": "2026-10-20T07:34:27.000Z",
"disabledAt": null,
"disabledReason": null,
"lastDelivery": null
},
"created": true
}这是 API 对此示例的响应,于2026年10月20日为本页生成;其中的相对时间和天数都以当天为准。
错误码
| 错误码 | 状态码 | 含义 |
|---|---|---|
| invalid_json | 400 | The body is not valid JSON or not a JSON object; or the input nests arrays and objects more than 64 levels deep (JSON documents inside string fields count too), or is too complex to inspect for private keys. |
| missing_field | 400 | A required field is absent or empty. |
| invalid_input | 400 | A field could not be parsed — the message explains exactly why. |
| private_key_rejected | 400 | The request contains private-key material (a PEM PRIVATE KEY block, an OpenSSH, SSH2 or PuTTY private key, or a JWK with "d"). Never send private keys anywhere. |
| webhook_url_refused | 400 | The webhook URL cannot receive deliveries: it is not https on port 443, its host is not a public DNS name, or the name does not resolve or resolves (at least partly) to a private or reserved address. |
| unauthorized | 401 | The Authorization or X-API-Key header is malformed, the API key is not valid (wrong checksum, unknown, revoked or expired), or the operation needs a key. |
| forbidden_scope | 403 | The API key is valid but lacks a scope this operation requires; the message names it. |
| webhook_limit_reached | 403 | The account already has as many webhook endpoints as its plan allows. |
| not_found | 404 | No operation exists at this path. |
| method_not_allowed | 405 | The path exists but does not accept this HTTP method; see the Allow header. |
| payload_too_large | 413 | The body exceeds 100 KB. |
| rate_limited | 429 | Too many requests — see Retry-After. |
| quota_exceeded | 429 | A daily or monthly quota of the API key's account is used up. X-Quota-Reset (and Retry-After) say when it starts over; retrying earlier cannot succeed. |
| internal_error | 500 | Unexpected server error. |
| service_unavailable | 503 | API keys cannot be verified right now (the key store is unreachable); retry shortly. |
| webhooks_unavailable | 503 | Webhooks are switched off on this server: it has no signing keys configured. |
| timeout | 504 | The operation did not finish within its time budget. |
Schema
WebhookEndpoint
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| id | string | 是 |
|
| url | string | 是 | |
| description | string | null | 是 | |
| events | string[] | 是 | Subscribed event types or families ("monitor.*"); empty receives every event. |
| enabled | boolean | 是 | |
| createdAt | string | 是 | UTC time, ISO 8601.
|
| disabledAt | string | null | 是 | |
| disabledReason | string | null | 是 | Why deliveries stopped: "consecutive_failures" after five failed deliveries in a row. |
| lastDelivery | WebhookDelivery | null | 是 |
WebhookDelivery
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| id | string | 是 | Delivery id, sent as ECDSA-Webhook-Id and as the body's id; the same on every retry. |
| type | string | 是 | Event type, e.g. "monitor.grade_changed" or "webhook.test". |
| status | string | 是 |
|
| attempts | integer | 是 |
|
| createdAt | string | 是 | UTC time, ISO 8601.
|
| lastAttemptAt | string | null | 是 | |
| lastStatusCode | integer | null | 是 | HTTP status of the last attempt; null when no response arrived. |
| lastError | string | null | 是 | Why the last attempt failed: "http_status" (any status outside 200–299, redirects included), "timeout", "address_refused", "tls_certificate", "endpoint_disabled", … ; null after a success. |
| nextAttemptAt | string | null | 是 | When a pending delivery is tried again. |
| deliveredAt | string | null | 是 |
Error
The error envelope of every non-2xx response.
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| error | object | 是 | |
| error.code | string | 是 | Machine-readable error code. Each response lists the codes it can carry; new codes may be added within v1, so treat an unknown code by its HTTP status. |
| error.message | string | 是 | Human-readable explanation (English). |
| error.requestId | string | 否 | The X-Request-Id of this request — quote it when reporting a problem. Sent since v1.1. |
| error.detail | object | 否 | Structured context for the error, such as the offending field; omitted when there is none.
|
| error.docs | string | 否 | Where the error codes are explained. Sent since v1.1.
|
所有操作都列在 API 参考中。