Webhooks

Receive real-time event notifications when things happen in your Arch Tools account — payments, credit alerts, errors, and more.

How It Works

Register an HTTPS endpoint with the events you want to subscribe to. When an event fires, Arch Tools sends a POST request to your URL with a signed JSON payload.

Every delivery includes an HMAC-SHA256 signature so you can verify it came from us. We retry failed deliveries up to 3 times with exponential backoff.

Event Types

EventFires WhenPayload Fields
payment.received A payment completes (one-time pack, subscription start, or renewal) type, credits_added, amount_usd, plan
credits.low Credit balance drops below 20% threshold after a tool call credits_remaining, tool_name, threshold
credits.depleted Credit balance hits zero credits_remaining, tool_name, message
agent.registered A new agent registers (for admin/platform webhooks) agent_id, email, tier
tool.error A tool call fails with an error tool_name, credits_charged

Payload Format

Every webhook delivery is a JSON POST with this structure:

{
  "id": "evt_a1b2c3d4e5f6g7h8i9j0k1l2",
  "event": "payment.received",
  "timestamp": "2026-03-17T14:30:00.000Z",
  "data": {
    "agent_id": "clx1abc2def3ghi4jkl",
    "type": "one_time",
    "credits_added": 5000,
    "amount_usd": "9.00",
    "stripe_session_id": "cs_test_..."
  }
}

Request Headers

HeaderDescription
Content-Typeapplication/json
X-Webhook-IdUnique event ID (for idempotency)
X-Webhook-EventEvent type (e.g. payment.received)
X-Webhook-SignatureHMAC-SHA256 signature: sha256=<hex>
X-Webhook-TimestampISO 8601 timestamp
User-AgentArchTools-Webhook/1.0

Verifying Signatures

Every delivery is signed with your webhook's secret using HMAC-SHA256. Always verify the signature before processing.

Node.js

import crypto from "crypto";

function verifyWebhook(body, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return signature === `sha256=${expected}`;
}

// In your Express handler:
app.post("/my-webhook", (req, res) => {
  const sig = req.headers["x-webhook-signature"];
  const rawBody = JSON.stringify(req.body);

  if (!verifyWebhook(rawBody, sig, "whsec_your_secret_here")) {
    return res.status(401).send("Invalid signature");
  }

  const event = req.body;
  console.log(`Received ${event.event}:`, event.data);
  res.status(200).send("OK");
});

Python

import hmac
import hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return signature == f"sha256={expected}"

# In your Flask handler:
@app.route("/my-webhook", methods=["POST"])
def webhook():
    sig = request.headers.get("X-Webhook-Signature", "")
    if not verify_webhook(request.data, sig, "whsec_your_secret_here"):
        return "Invalid signature", 401

    event = request.json
    print(f"Received {event['event']}: {event['data']}")
    return "OK", 200

API Reference

Register a Webhook

POST /api/v1/webhooks/register
curl -X POST https://archtools.dev/api/v1/webhooks/register \
-H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhook",
    "events": ["payment.received", "credits.low"]
  }'
⚠️ Important: The webhook secret is only returned once at registration. Save it immediately — you'll need it to verify signatures.

List Webhooks

GET /api/v1/webhooks
curl https://archtools.dev/api/v1/webhooks \
-H "x-api-key: YOUR_API_KEY"

Delete a Webhook

DELETE /api/v1/webhooks/:id
curl -X DELETE https://archtools.dev/api/v1/webhooks/WEBHOOK_ID \
-H "x-api-key: YOUR_API_KEY"

Send a Test Event

POST /api/v1/webhooks/test
curl -X POST https://archtools.dev/api/v1/webhooks/test \
-H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "webhook_id": "OPTIONAL_SPECIFIC_WEBHOOK_ID" }'

Retry Policy

If your endpoint returns a non-2xx status or times out (10s), we retry up to 3 times with exponential backoff:

AttemptDelay
1st retry1 second
2nd retry4 seconds
3rd retry9 seconds

Client errors (4xx except 429) are not retried — fix your endpoint and use the test endpoint to verify.

Best Practices