> ## Documentation Index
> Fetch the complete documentation index at: https://ekso.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Rule endpoints

> Send rule-fire events to external URLs — envelope shape, URL restrictions, and receiver examples in Node and C#.

## Overview

When a rule with an **Endpoint action** fires, Ekso POSTs a structured envelope to the configured URL. This is the integration point for Slack alerts, Linear ticket creation, PagerDuty incidents, agent workers, or any webhook-style consumer.

Ekso's egress is **fire-and-forget** by design. Ekso does not inspect the response status — a 200, a 500, or a timeout all look the same from Ekso's side. Receivers that need delivery guarantees should idempotently persist each payload and handle their own retry.

## Request shape

```http theme={null}
POST https://your-receiver.example.com/webhook
Accept: application/json
Content-Type: application/json
<admin-configured headers>

{
  "ruleId": "rule_01HX...",
  "ruleName": "P0 ticket created",
  "tenantId": "tenant_...",
  "firedAt": "2026-04-21T14:02:33.112Z",
  "event": "OnItemAdded",
  "payload": { /* DataItem */ }
}
```

### Envelope fields

| Field      | Type                    | Description                                                          |
| ---------- | ----------------------- | -------------------------------------------------------------------- |
| `ruleId`   | string                  | The Ekso rule ID. Stable across fires.                               |
| `ruleName` | string                  | The rule name as configured in **Settings > Rule**.                  |
| `tenantId` | string                  | Your tenant ID.                                                      |
| `firedAt`  | ISO-8601 datetime (UTC) | When the rule fired.                                                 |
| `event`    | string                  | One of `OnItemAdd`, `OnItemAdded`, `OnItemUpdated`, `OnItemDeleted`. |
| `payload`  | object                  | The `DataItem` that triggered the rule.                              |

## URL restrictions

Ekso blocks egress to URLs that could expose internal infrastructure. Configured URLs must pass both admin-time and connect-time validation.

### Blocked at admin-time

* Malformed URLs
* Non-HTTPS schemes (HTTP is allowed in development only)
* Literal private-range IPs: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`
* Loopback: `127.0.0.0/8`, `::1`
* Link-local: `169.254.0.0/16`, `fe80::/10`
* Unique-local IPv6: `fc00::/7`
* Cloud metadata hosts: `169.254.169.254`, `metadata.google.internal`, `metadata.azure.com`

### Blocked at connect-time

Hostnames are re-resolved at connect time. If a hostname resolves to any blocked range, the POST fails. This defends against DNS rebinding where a public hostname flips to a private IP between configuration and the actual POST.

## Timeout

Each POST has a 5-second timeout. Slow receivers do not delay rule processing — Ekso fires and moves on.

## Authenticating Ekso from your side

Ekso does not sign requests in the current version. Receivers that need to verify requests came from Ekso have two options:

* **Secret path component** — include a hard-to-guess segment in the URL you configure (e.g. `https://receiver.example.com/hook/abc-123-secret`). Treat the path as the shared secret.
* **Shared bearer token** — add an admin-configured header (e.g. `"Authorization: Bearer <your-token>"`) when you create the endpoint action. The token is sent verbatim on every POST.

<Note>
  HMAC-SHA256 request signing is on the roadmap. When it ships, receivers will be able to verify request authenticity without relying on URL secrecy or shared tokens.
</Note>

## Receiver examples

### Node (Express)

```javascript theme={null}
import express from "express";

const app = express();

app.post("/webhook", express.json(), (req, res) => {
  if (req.headers.authorization !== `Bearer ${process.env.EKSO_WEBHOOK_TOKEN}`) {
    return res.sendStatus(401);
  }

  const { ruleId, ruleName, event, firedAt, payload } = req.body;
  console.log(`Rule ${ruleName} (${ruleId}) fired on ${event} at ${firedAt}`);

  // ... dispatch to your system ...

  res.sendStatus(200);
});

app.listen(3000);
```

### C# (ASP.NET Core minimal API)

```csharp theme={null}
app.MapPost("/webhook", (HttpContext ctx, RuleFireEnvelope envelope, IConfiguration config, ILogger<Program> logger) =>
{
    if (ctx.Request.Headers.Authorization != $"Bearer {config["EksoWebhookToken"]}")
        return Results.Unauthorized();

    logger.LogInformation("Rule {Name} ({Id}) fired on {Event} at {FiredAt}",
        envelope.RuleName, envelope.RuleId, envelope.Event, envelope.FiredAt);

    // ... dispatch to your system ...
    return Results.Ok();
});

public record RuleFireEnvelope(
    string RuleId,
    string RuleName,
    string TenantId,
    DateTime FiredAt,
    string Event,
    object Payload);
```

## Configuring an endpoint action

You can add an endpoint action to a rule two ways:

* **UI** — **Settings > Rule > Actions**, add a new action of type **Endpoint**.
* **MCP** — call the [`add_endpoint_action`](/mcp-tools/rule#add_endpoint_action) tool from an AI agent.

## Delivery semantics

* **Best-effort.** Ekso does not retry failed POSTs. If your receiver is down, the delivery is lost. Persist at your end if you need durability.
* **Independent.** A rule can have multiple endpoint actions. They fire independently — one failing receiver does not affect the others.
* **Pre-persistence timing.** Endpoint POSTs fire at rule-fire time, which may precede the database commit of the triggering item by milliseconds. Treat the payload as *"event observed"* rather than *"state of record"*. If you need read-after-write consistency, re-fetch via the [Items API](/api-reference/introduction) using the `payload.id` field.
* **No response inspection.** Ekso ignores whatever you return. Return any status code that suits your infrastructure.
