Skip to content

AxmeAI/reliable-delivery-without-webhooks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Reliable Delivery Without Webhooks

Webhook retry is everyone's problem. AXME eliminates it with 5 delivery bindings - SSE stream, poll, HTTP push, inbox, internal - all with at-least-once delivery.

Stop building webhook retry logic. Exponential backoff? Jitter? Dead letter queues? Thundering herd? AXME gives you 5 delivery options with at-least-once guarantees. The receiver chooses how to get messages. No webhook endpoint needed.

Alpha - Built with AXME (AXP Intent Protocol). cloud.axme.ai - hello@axme.ai


The Problem

You need to deliver a message from service A to service B reliably. Your options today:

Option 1: Webhooks
  - Build an HTTP endpoint on the receiver
  - Implement HMAC signature verification
  - Handle duplicate deliveries (idempotency)
  - Build retry with exponential backoff + jitter
  - Dead letter queue for permanent failures
  - Thundering herd when retries all fire at once
  - Log, monitor, alert on delivery failures

Option 2: Message queue (RabbitMQ, SQS, Kafka)
  - Deploy and operate the queue infrastructure
  - Build producers and consumers
  - Handle poison messages, DLQ, offset management
  - Monitor queue depth, consumer lag, partition rebalancing

Both options: 200+ lines of infrastructure code before any business logic.

The Solution: 5 Delivery Bindings

Sender: client.send_intent(payload)
Receiver: picks delivery mode that fits

  stream  - SSE (real-time, agent keeps connection open)
  poll    - GET polling (serverless, cron-based consumers)
  http    - Webhook POST (traditional, but AXME handles retry)
  inbox   - Human inbox (for human-in-the-loop)
  internal - Platform-internal (reminders, escalations)

The sender doesn't care how the receiver gets the message. AXME delivers with at-least-once guarantees on all modes.


Quick Start

Python

pip install axme
export AXME_API_KEY="your-key"   # Get one: axme login
from axme import AxmeClient, AxmeClientConfig
import os

client = AxmeClient(AxmeClientConfig(api_key=os.environ["AXME_API_KEY"]))

# Send an order - AXME delivers to the processor
intent_id = client.send_intent({
    "intent_type": "intent.order.process.v1",
    "to_agent": "agent://myorg/production/order-processor",
    "payload": {
        "order_id": "ORD-2026-00142",
        "customer": "acme-corp",
        "total": 4999.50,
    },
})

print(f"Submitted: {intent_id}")

# Wait for result - no polling on your side
result = client.wait_for(intent_id)
print(f"Done: {result['status']}")

TypeScript

npm install @axme/axme
import { AxmeClient } from "@axme/axme";

const client = new AxmeClient({ apiKey: process.env.AXME_API_KEY! });

const intentId = await client.sendIntent({
  intentType: "intent.order.process.v1",
  toAgent: "agent://myorg/production/order-processor",
  payload: {
    orderId: "ORD-2026-00142",
    customer: "acme-corp",
    total: 4999.5,
  },
});

console.log(`Submitted: ${intentId}`);
const result = await client.waitFor(intentId);
console.log(`Done: ${result.status}`);

More Languages

Language Directory Install
Python python/ pip install axme
TypeScript typescript/ npm install @axme/axme
Go go/ go get github.com/AxmeAI/axme-sdk-go

Before / After

Before: Webhook Retry Infrastructure (200+ lines)

# Webhook endpoint (receiver must build this)
@app.post("/webhooks/orders")
async def receive_order(req):
    # Verify HMAC signature
    signature = req.headers.get("x-webhook-signature")
    if not verify_hmac(signature, req.body, WEBHOOK_SECRET):
        return {"error": "invalid signature"}, 401

    # Check idempotency (handle duplicate deliveries)
    idempotency_key = req.headers.get("x-idempotency-key")
    if db.exists("processed_webhooks", idempotency_key):
        return {"status": "already processed"}, 200

    # Process the order
    process_order(req.json())
    db.insert("processed_webhooks", idempotency_key)
    return {"status": "ok"}, 200

# Sender must build retry logic
async def send_with_retry(url, payload, max_retries=5):
    for attempt in range(max_retries):
        try:
            resp = requests.post(url, json=payload, headers=sign(payload))
            if resp.status_code == 200:
                return resp
            if resp.status_code >= 500:
                raise RetryableError()
        except (ConnectionError, Timeout, RetryableError):
            delay = min(2 ** attempt + random.uniform(0, 1), 300)  # backoff + jitter
            await asyncio.sleep(delay)
    # Dead letter queue
    dlq.send(payload)
    alert("Webhook delivery failed after 5 retries")

# Plus: DLQ consumer, monitoring, alerting, cleanup...

After: AXME Delivery (4 lines)

intent_id = client.send_intent({
    "intent_type": "intent.order.process.v1",
    "to_agent": "agent://myorg/production/order-processor",
    "payload": {"order_id": "ORD-2026-00142", "customer": "acme-corp", "total": 4999.50},
})
result = client.wait_for(intent_id)

No webhook endpoint. No HMAC verification. No idempotency table. No retry logic. No backoff. No jitter. No DLQ. No monitoring. AXME handles all of it.


5 Delivery Bindings

Binding Transport Best For Receiver Builds
stream SSE (server-sent events) Real-time agents, services SSE client (3 lines)
poll GET polling Serverless, cron jobs GET request (1 line)
http Webhook POST Services with HTTP endpoint Nothing (AXME handles retry)
inbox Human inbox Approvals, reviews Nothing (CLI/email/form)
internal Platform-internal Reminders, escalations Nothing (automatic)

The receiver chooses the binding. The sender just calls send_intent().


How It Works

+-----------+  send_intent()   +----------------+  deliver    +-----------+
|           | ---------------> |                | ---------> |           |
|  Sender   |                  |   AXME Cloud   |  (stream,  | Receiver  |
|           | <- wait_for() -- |   (platform)   |   poll,    |  (agent)  |
|           |  resumes when    |                |   http,    |           |
|           |  receiver done   | at-least-once  |   inbox)   | processes |
|           |                  | delivery on    |            | and       |
|           |                  | ALL bindings   | <- resume  | resumes   |
+-----------+                  +----------------+            +-----------+
  1. Sender submits an intent with payload and target
  2. AXME delivers via the receiver's configured binding
  3. If delivery fails, AXME retries (up to max_delivery_attempts)
  4. Receiver processes and resumes with result
  5. Sender gets the result via SSE stream (real-time, no polling)

Run the Full Example

Prerequisites

# Install CLI (one-time)
curl -fsSL https://raw.githubusercontent.com/AxmeAI/axme-cli/main/install.sh | sh
# Open a new terminal, or run the "source" command shown by the installer

# Log in
axme login

# Install Python SDK
pip install axme

Terminal 1 - submit the scenario

axme scenarios apply scenario.json
# Note the intent_id in the output

Terminal 2 - start the agent

Get the agent key after scenario apply:

# macOS
cat ~/Library/Application\ Support/axme/scenario-agents.json | grep -A2 order-processor-demo

# Linux
cat ~/.config/axme/scenario-agents.json | grep -A2 order-processor-demo

Run in your language of choice:

# Python
AXME_API_KEY=<agent-key> python agent.py

# TypeScript (requires Node 20+)
cd typescript && npm install
AXME_API_KEY=<agent-key> npx tsx agent.ts

# Go
cd go && go run ./cmd/agent/

Verify

axme intents get <intent_id>
# lifecycle_status: COMPLETED

Related


Built with AXME (AXP Intent Protocol).

About

Webhook retry is everyone's problem. 5 delivery bindings with at-least-once guarantees. No webhook endpoints needed.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors