Skip to content
ecdsa.com

Webhooks

Send a test event

POSThttps://ecdsa.com/api/v1/webhooks/test

Sends a "webhook.test" event to one endpoint of the API key's account — also to one that is switched off — and answers once the first attempt is made: status delivered, or pending with the receiver's HTTP status or the failure in lastError (a failed test event is retried like any other delivery). The event's data is {"endpoint": {"id", "url"}}. At most 10 test events per endpoint and hour (rate_limited with Retry-After). 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 key
Required, with the scope webhooks
Quota cost with a key
1 unit per call
Rate limit
60 requests/min per IP

Request

JSON body: SendWebhookTestRequest

FieldTypeRequiredDescription
idstringyes

The endpoint's id (listWebhooks lists them).

Responses

200The test delivery after its first attempt.

FieldTypeRequiredDescription
deliveryWebhookDeliveryyes

Headers: X-Request-Id · X-RateLimit-Limit · X-RateLimit-Remaining · X-RateLimit-Reset · X-Quota-Limit · X-Quota-Remaining · X-Quota-Reset

StatusMeaningCodes
400Bad 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.
401Unauthorized.
403Forbidden.
404Not found.
405Method not allowed.
413Payload too large.
429Too many requests.
500Internal server error.
503Service unavailable.
504Gateway timeout.

Every error answer carries the JSON envelope; the error table says what each code means and what to do.

Example

Send a test event

curl
curl -sS 'https://ecdsa.com/api/v1/webhooks/test' \
  -H "authorization: Bearer $ECDSA_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
  "id": "7349e17c-8b41-4fc1-b1a9-dca88578ca1c"
}'
JavaScript (fetch)
const response = await fetch("https://ecdsa.com/api/v1/webhooks/test", {
  method: "POST",
  headers: { "content-type": "application/json", authorization: `Bearer ${process.env.ECDSA_API_KEY}` },
  body: JSON.stringify({
    "id": "7349e17c-8b41-4fc1-b1a9-dca88578ca1c"
  }),
});
console.log(response.status, await response.json());
Python (httpx)
import os

import httpx

response = httpx.post(
    "https://ecdsa.com/api/v1/webhooks/test",
    headers={"authorization": f"Bearer {os.environ['ECDSA_API_KEY']}"},
    json={
        "id": "7349e17c-8b41-4fc1-b1a9-dca88578ca1c",
    },
    timeout=30.0,
)
print(response.status_code, response.json())
Response 200
{
  "delivery": {
    "id": "6491ad3b-5b71-417e-a73a-efdb977de0e1",
    "type": "webhook.test",
    "status": "delivered",
    "attempts": 1,
    "createdAt": "2026-10-20T07:34:27.000Z",
    "lastAttemptAt": "2026-10-20T07:34:27.000Z",
    "lastStatusCode": 204,
    "lastError": null,
    "nextAttemptAt": null,
    "deliveredAt": "2026-10-20T07:34:27.000Z"
  }
}

The API’s answer to this example, produced for this page on October 20, 2026; relative times and day counts in it refer to that day.

Error codes

CodeStatusMeaning
invalid_json400The 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_field400A required field is absent or empty.
private_key_rejected400The 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.
unauthorized401The 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_scope403The API key is valid but lacks a scope this operation requires; the message names it.
not_found404No operation exists at this path.
webhook_not_found404The API key's account has no webhook endpoint with this id.
method_not_allowed405The path exists but does not accept this HTTP method; see the Allow header.
payload_too_large413The body exceeds 100 KB.
rate_limited429Too many requests — see Retry-After.
quota_exceeded429A 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_error500Unexpected server error.
service_unavailable503API keys cannot be verified right now (the key store is unreachable); retry shortly.
webhooks_unavailable503Webhooks are switched off on this server: it has no signing keys configured.
timeout504The operation did not finish within its time budget.

Schemas

WebhookDelivery

FieldTypeRequiredDescription
idstringyes

Delivery id, sent as ECDSA-Webhook-Id and as the body's id; the same on every retry.

typestringyes

Event type, e.g. "monitor.grade_changed" or "webhook.test".

statusstringyes
  • one of "pending", "delivered", or "failed"
attemptsintegeryes
  • at least 0
createdAtstringyes

UTC time, ISO 8601.

  • format date-time
lastAttemptAtstring | nullyes
lastStatusCodeinteger | nullyes

HTTP status of the last attempt; null when no response arrived.

lastErrorstring | nullyes

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.

nextAttemptAtstring | nullyes

When a pending delivery is tried again.

deliveredAtstring | nullyes

Error

The error envelope of every non-2xx response.

FieldTypeRequiredDescription
errorobjectyes
error.codestringyes

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.messagestringyes

Human-readable explanation (English).

error.requestIdstringno

The X-Request-Id of this request — quote it when reporting a problem. Sent since v1.1.

error.detailobjectno

Structured context for the error, such as the offending field; omitted when there is none.

  • any member names
error.docsstringno

Where the error codes are explained. Sent since v1.1.

  • format uri

All operations are listed in the API reference.