Two agents, one limit, one refused.
All posts
5 min read
by

One agent decision, many downstream systems

Publish once and deliver to every endpoint that cares, each with its own retries and delivery tracking. For agents that have to tell more than one system.

url-groupsfan-outagentsarchitecture

Editor's note, 3 October 2026. This post covers an earlier namespace. It still runs on the same API. Anlyon's product today is shared limits and receipts for AI agent actions.

When an agent decides something, more than one system usually needs to hear about it: the CRM, the ledger, the audit sink, the customer's webhook. Writing that fan-out by hand means four calls, four retry policies, and four ways to half-fail. A URL Group lets the agent publish once, and each endpoint receives it independently with its own retry logic and delivery tracking.

What Are URL Groups?

URL Groups let you define a collection of endpoints and publish messages to all of them with a single API call. Each endpoint receives the message independently, with its own retry logic and delivery tracking.

Use cases:

  • Broadcasting events to multiple microservices
  • Sending webhooks to multiple third-party services
  • Notifying multiple systems of state changes
  • Implementing pub/sub patterns

Creating a URL Group

Basic Setup

import { Client } from '@anlyonhq/sdk';

const client = new Client({
  apiKey: process.env.ANLYON_API_KEY!,
});

// Create a URL group with multiple endpoints
const group = await client.urlGroups.create({
  name: 'Production Webhooks',
  endpoints: [
    { 
      url: 'https://service1.example.com/webhook',
      name: 'User Service',
    },
    { 
      url: 'https://service2.example.com/webhook',
      name: 'Notification Service',
    },
    { 
      url: 'https://service3.example.com/webhook',
      name: 'Analytics Service',
    },
  ],
});

console.log(`Created group: ${group.data.id}`);

Custom Retry Configuration

Each endpoint can have its own retry configuration:

const group = await client.urlGroups.create({
  name: 'Critical Services',
  endpoints: [
    {
      url: 'https://critical-service.com/webhook',
      name: 'Critical Service',
      retryCount: 10, // More retries for critical services
    },
    {
      url: 'https://optional-service.com/webhook',
      name: 'Optional Service',
      retryCount: 3, // Fewer retries for optional services
    },
  ],
});

Publishing to a Group

Basic Broadcast

// Publish to all endpoints in the group
const result = await client.urlGroups.publish(group.data.id, {
  method: 'POST',
  body: {
    event: 'user.created',
    userId: '123',
    timestamp: new Date().toISOString(),
  },
});

console.log(`Published to ${result.data.total} endpoints`);
console.log(`Successful: ${result.data.successful}`);
console.log(`Failed: ${result.data.failed}`);

Scheduling a broadcast

You can also broadcast at a future time. executeAt is a Unix timestamp in seconds, so "in a minute" is the current time plus 60:

await client.urlGroups.publish(group.data.id, {
  method: 'POST',
  body: { event: 'order.processed', orderId: '456' },
  executeAt: Math.floor(Date.now() / 1000) + 60,
});

Managing Groups

List All Groups

const groups = await client.urlGroups.list();
groups.data.forEach(group => {
  console.log(`${group.name}: ${group.id}`);
});

Get Group Details

const group = await client.urlGroups.get('group-id');
console.log(`Group: ${group.data.name}`);
console.log(`Endpoints: ${group.data.endpoints.length}`);
group.data.endpoints.forEach(endpoint => {
  console.log(`  - ${endpoint.name}: ${endpoint.url}`);
});

Add Endpoints

Dynamically add endpoints to an existing group:

await client.urlGroups.addEndpoint('group-id', {
  url: 'https://new-service.com/webhook',
  name: 'New Service',
  retryCount: 5,
});

Remove Endpoints

await client.urlGroups.removeEndpoint('group-id', 'endpoint-id');

Update Group Name

await client.urlGroups.update('group-id', {
  name: 'Updated Group Name',
});

Delete a Group

await client.urlGroups.delete('group-id');

Real-World Examples

Event-Driven Architecture

In a microservices architecture, URL Groups make it easy to broadcast events:

// When a user signs up, notify multiple services
const userCreatedGroup = await client.urlGroups.get('user-events-group');

await client.urlGroups.publish(userCreatedGroup.data.id, {
  method: 'POST',
  body: {
    event: 'user.created',
    userId: newUser.id,
    email: newUser.email,
    timestamp: new Date().toISOString(),
  },
});

// This automatically notifies:
// - Email service (send welcome email)
// - Analytics service (track signup)
// - Notification service (send push notification)
// - CRM service (create contact)

Third-Party Webhook Broadcasting

Broadcast webhooks to multiple third-party services:

// Create a group for payment webhooks
const paymentGroup = await client.urlGroups.create({
  name: 'Payment Webhooks',
  endpoints: [
    { url: 'https://stripe.com/webhook', name: 'Stripe' },
    { url: 'https://paypal.com/webhook', name: 'PayPal' },
    { url: 'https://internal-api.com/payments', name: 'Internal API' },
  ],
});

// Broadcast payment event
await client.urlGroups.publish(paymentGroup.data.id, {
  method: 'POST',
  body: {
    event: 'payment.completed',
    amount: 99.99,
    currency: 'USD',
    transactionId: 'txn_123',
  },
});

Multi-Region Deployment

Notify services across multiple regions:

const multiRegionGroup = await client.urlGroups.create({
  name: 'Multi-Region Services',
  endpoints: [
    { url: 'https://us-east.api.example.com/webhook', name: 'US East' },
    { url: 'https://eu-west.api.example.com/webhook', name: 'EU West' },
    { url: 'https://asia-pac.api.example.com/webhook', name: 'Asia Pacific' },
  ],
});

// Broadcast configuration update
await client.urlGroups.publish(multiRegionGroup.data.id, {
  method: 'POST',
  body: {
    event: 'config.updated',
    config: { featureFlag: true },
  },
});

Best Practices

  1. Group by purpose: Create groups based on event types or service categories
  2. Use descriptive names: Make it easy to identify groups in your dashboard
  3. Set appropriate retries: Critical services need more retries than optional ones
  4. Monitor delivery: Track success rates for each endpoint
  5. Handle failures gracefully: Some endpoints may fail while others succeed
  6. Limit group size: Keep groups under 50 endpoints for optimal performance
  7. Use idempotent endpoints: Ensure your webhooks can handle duplicate deliveries

Error Handling

When publishing to a group, some endpoints may succeed while others fail:

const result = await client.urlGroups.publish('group-id', {
  body: { event: 'test' },
});

if (result.data.failed > 0) {
  console.warn(`${result.data.failed} endpoints failed to receive the message`);
  // Check individual endpoint status
  result.data.results.forEach(endpointResult => {
    if (!endpointResult.success) {
      console.error(`Failed: ${endpointResult.endpointUrl} - ${endpointResult.error}`);
    }
  });
}

Performance Considerations

  • Parallel delivery: All endpoints receive messages in parallel
  • Independent retries: Each endpoint retries independently
  • Non-blocking: Failed endpoints don't block successful ones
  • Scalable: Groups can handle high message volumes

Comparison: URL Groups vs Individual Messages

Without URL Groups (Manual)

// Publish to each endpoint individually
const endpoints = [
  'https://service1.com/webhook',
  'https://service2.com/webhook',
  'https://service3.com/webhook',
];

for (const url of endpoints) {
  await client.messages.publish({
    url,
    body: { event: 'test' },
  });
}
// Problems:
// - More API calls
// - Harder to manage
// - No aggregate tracking
// Single API call broadcasts to all
await client.urlGroups.publish('group-id', {
  body: { event: 'test' },
});
// Benefits:
// - Single API call
// - Centralized management
// - Aggregate tracking
// - Independent retries

Next Steps

Start broadcasting with URL Groups today! 📡

Free tier, no credit card. One command if you use Claude or Cursor.

$ claude mcp add anlyon -- npx -y @anlyonhq/mcp-server