EkireDocs

Webhooks

Register one endpoint and Ekire sends it a signed HTTP POST every time a subscribed event fires — a server boots, a payment lands, a ticket gets a reply — instead of polling the API on a timer. Each delivery is signed, and every attempt is logged with what was sent and how your endpoint responded.

You'll find everything under Webhooks in the console.

Your webhook endpoints — each shows its target URL, subscribed events, health, and a log of recent deliveries.
Your webhook endpoints — each shows its target URL, subscribed events, health, and a log of recent deliveries.

Create an endpoint

Creating an endpoint pairs one URL with the events you want sent to it and generates a signing secret, shown once — copy it right then and store it somewhere safe.

Open the new-webhook form

Click New webhook on the Webhooks page.

Enter your endpoint URL

Paste the public URL that should receive events, e.g. https://api.example.com/hooks/ekire. This is where we'll POST every delivery.

Choose the events

Tick the events you care about — grouped under Servers, Billing, and Support — or use Select all. You must pick at least one.

Add a description (optional)

A short note about what the endpoint is for, so it's recognizable later in the list.

Create and copy the secret

Click Create webhook. Your signing secret (it starts with whsec_) appears once at the top of the page. Copy it now — you'll need it to verify every delivery, and it can't be shown again.

Endpoints must be public

The URL must be a public HTTPS (or HTTP) address that resolves to a non-internal host. Endpoints pointing at private, loopback, or otherwise internal addresses are rejected when you save — and re-checked on every delivery.

Once created, an endpoint can be paused with its toggle or deleted at any time in the console; editing its URL, events, or description is done through the API. Use the Test button on a row to send a synthetic ping event so you can confirm your endpoint receives and verifies us.

Events you can subscribe to

Every delivery names its type in the X-Ekire-Event header and in the envelope's event field. These are the events available today:

EventFires when
instance.createdA server is created
instance.destroyedA server is destroyed
instance.startedA server is powered on
instance.stoppedA server is powered off
instance.rebootedA server is rebooted
instance.resizedA server is resized to a different plan
payment.succeededA payment to your wallet succeeds
wallet.low_balanceYour wallet balance runs low
account.suspendedYour account is suspended
account.reactivatedYour account is reactivated
ticket.createdA support ticket is opened
ticket.repliedA support ticket receives a reply

What a delivery looks like

Each delivery is an HTTP POST with a JSON envelope. The data field carries the event-specific payload:

{
  "id": "8f2b1c9e-…",
  "event": "instance.created",
  "createdAt": "2026-07-19T10:00:00.000Z",
  "data": { "…": "event-specific fields" }
}

Alongside the body, we send these headers:

HeaderValue
Content-Typeapplication/json
User-AgentEkire-Webhooks/1
X-Ekire-EventThe event type, e.g. instance.created
X-Ekire-DeliveryThe unique delivery ID (matches id in the body)
X-Ekire-TimestampUnix time (seconds) the delivery was signed
X-Ekire-SignatureThe signature, as sha256=<hex digest>

Verifying the signature

Every delivery is signed with HMAC-SHA256 using your endpoint's signing secret. This lets you confirm a request genuinely came from Ekire and wasn't tampered with. The signed value is the timestamp and the raw request body joined with a . — that is, "{timestamp}.{body}" — and the resulting digest is sent as a lowercase hex string in the X-Ekire-Signature header, prefixed with sha256=.

To verify a delivery:

  1. Read X-Ekire-Timestamp and X-Ekire-Signature from the request headers.
  2. Compute HMAC-SHA256("{timestamp}.{raw_body}", your_secret) and hex-encode it.
  3. Compare sha256=<your digest> to the header value using a constant-time comparison.
  4. Optionally reject deliveries whose timestamp is too old to blunt replay attempts.

Use the raw request body exactly as received — re-serializing the JSON can change bytes and break the match.

# Inputs: raw_body, headers, and your signing secret (whsec_…)
 
timestamp = headers["X-Ekire-Timestamp"]
signed    = timestamp + "." + raw_body            # note the "." separator
expected  = "sha256=" + hmac_sha256_hex(secret, signed)
 
# Constant-time compare — reject if it doesn't match.
if not constant_time_equal(expected, headers["X-Ekire-Signature"]):
    reject(400)
 
# Optional replay defense: reject stale deliveries (e.g. older than 5 min).
if abs(now_unix() - to_int(timestamp)) > 300:
    reject(400)
 
# Signature is valid — process the event.

Keep your secret secret

Anyone with the signing secret can forge deliveries. Store it in a secrets manager, never in client-side code or source control. If it may be exposed, delete the endpoint and create a new one to rotate it.

Delivery and retries

Your endpoint should return a 2xx status quickly to acknowledge a delivery. Anything else — a non-2xx status, a redirect, a timeout, or a connection error — counts as a failed attempt.

  • Timeout. We wait up to 10 seconds for a response, so acknowledge fast and do heavy work afterward.
  • Retries. A failed delivery is retried with exponential backoff — up to 6 attempts total, spanning roughly 10 seconds to a few minutes. Because a delivery can arrive more than once, treat the X-Ekire-Delivery ID as an idempotency key.
  • No redirects. We don't follow 3xx responses; a redirect is treated as a failure. Point the endpoint at its final URL.
  • Health and auto-pause. Each row shows whether an endpoint is Healthy or Failing. After a sustained run of consecutive failures, an endpoint is automatically paused so we stop hammering a dead URL. Fix your endpoint and re-enable it — that clears the failure streak.
  • Delivery log. Expand any endpoint to see its recent deliveries with event type, HTTP response status, any error, and timestamp. The log keeps the most recent deliveries as a rolling history, not a permanent archive.