---
title: "Managing endpoints — Webhooks for Laravel | Roundly"
description: "Find, list, update, clear, enable, disable and delete endpoints in both directions, the HasWebhooks owner trait and every option limit."
url: https://roundly-consulting.com/open-source/docs/webhooks-for-laravel/managing-endpoints
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Webhooks for Laravel](https://roundly-consulting.com/open-source/docs/webhooks-for-laravel.md)

# Managing endpoints

Both directions share one management API on their sub-accessor — Webhooks::inbound() and Webhooks::outbound() for the whole app, Webhooks::for($owner)->inbound() and ->outbound() for one owner:

| Method | Inbound | Outbound |
| --- | --- | --- |
| `find(int\|string $id)` | InboundEndpoint | OutboundEndpoint — unknown, soft-deleted or another owner’s id: EndpointNotFoundException |
| `endpoints()` | Collection<InboundEndpoint> | Collection<OutboundEndpoint> — ordered by id, owner eager-loaded |
| `update($endpoint, $changes)` | InboundEndpointChanges | OutboundEndpointChanges |
| `enable($endpoint)` | Clears disabled\_at and disabled\_reason | The same, and consecutive\_failures back to 0 |
| `disable($endpoint)` | disabled\_reason = manual; the URL answers 404 | EndpointDisabled(manual); pending and retrying deliveries cancelled |
| `delete($endpoint)` | Soft delete; secrets wiped at once | Soft delete; secrets, URL and custom headers wiped at once; pending deliveries cancelled |

## Inbound

```php
$endpoint = Webhooks::for($team)->inbound()->find($id);   // another owner's id: EndpointNotFoundException
Webhooks::for($team)->inbound()->endpoints();               // every endpoint of $team, owner eager-loaded

Webhooks::inbound()->update($endpoint, new InboundEndpointChanges(
    name: 'Billing provider',
    handler: 'billing',
    options: new InboundEndpointOptions(rateLimitPerMinute: 120, toleranceSeconds: 60),
));
Webhooks::inbound()->update($endpoint, new InboundEndpointChanges(clear: ['rate_limit_per_minute', 'tolerance_seconds']));   // back to the config defaults

Webhooks::inbound()->disable($endpoint);   // the URL answers 404 until it is enabled again
Webhooks::inbound()->enable($endpoint);
Webhooks::inbound()->delete($endpoint);    // secrets wiped at once, the URL answers 404
```

InboundEndpointChanges(?string $name, ?string $handler, ?string $scheme, ?InboundEndpointOptions $options, list<string> $clear = \[\]) — only the fields you give change. clear sets nullable fields back to null, so the config default applies: name, handler, rate\_limit\_per\_minute, max\_payload\_bytes, allowed\_content\_types, tolerance\_seconds, scheme\_options. Any other name throws InvalidEndpointException.

## Outbound

```php
$endpoint = Webhooks::for($team)->outbound()->find($id);

Webhooks::outbound()->update($endpoint, new OutboundEndpointChanges(
    url: $newUrl,                                    // judged by the SSRF guard again
    events: ['invoice.*', 'customer.created'],
    headers: ['X-Tenant' => 'acme'],                 // replaces the custom headers
    options: new OutboundEndpointOptions(rateLimitPerMinute: 10, description: 'ERP sync'),
));
Webhooks::outbound()->update($endpoint, new OutboundEndpointChanges(clear: ['headers', 'rate_limit_per_minute']));

Webhooks::outbound()->disable($endpoint);   // EndpointDisabled (manual); pending deliveries are cancelled
Webhooks::outbound()->enable($endpoint);    // the failure counter starts again from 0
Webhooks::outbound()->delete($endpoint);    // secrets, URL and custom headers wiped at once
```

OutboundEndpointChanges(?string $url, ?array $events, ?string $name, ?array $headers, ?string $scheme, ?OutboundEndpointOptions $options, list<string> $clear = \[\]). A new URL is judged like a new endpoint’s (DestinationBlockedException); new headers are validated and replace the old set (an empty array clears them). clear accepts name, description, headers, rate\_limit\_per\_minute and scheme\_options. From the console: webhooks:enable {inbound|outbound} {endpoint} and webhooks:endpoints.

## The owner trait

Put Concerns\\HasWebhooks on any model that owns endpoints — a team, an account, a user. No tenancy is assumed, and endpoints without an owner work too:

```php
use RoundlyConsulting\Webhooks\Concerns\HasWebhooks;

final class Team extends Model
{
    use HasWebhooks;
}
```

```php
$team->webhooks()->outbound()->create($url, events: ['invoice.*']);   // = Webhooks::for($team)
$team->outboundWebhookEndpoints()->where('enabled', true)->get();        // MorphMany relations
$team->inboundWebhookEndpoints()->count();
$team->broadcastWebhook('invoice.paid', ['invoice' => 42]);
```

| Method | Returns |
| --- | --- |
| `inboundWebhookEndpoints()` | `MorphMany<InboundEndpoint>` |
| `outboundWebhookEndpoints()` | `MorphMany<OutboundEndpoint>` |
| `webhooks()` | OwnerWebhooks — the same handle as Webhooks::for($this) |
| `broadcastWebhook(string $eventType, array\|Arrayable\|JsonSerializable $payload)` | Collection<OutboundDelivery> — Webhooks::for($this)->broadcast(…) |

The trait goes through the manager, so Webhooks::fake() records its broadcasts.

## Option limits

InboundEndpointOptions and OutboundEndpointOptions are validated on create and update; a value out of range throws InvalidEndpointException naming the option:

| Option | Limit |
| --- | --- |
| `rateLimitPerMinute` | Both directions: 1 – 1 000 000 |
| `maxPayloadBytes` | Inbound: 1 – inbound.max\_payload\_bytes\_ceiling (1 MB) |
| `contentTypes` | Inbound: a non-empty list of type/subtype (\* allowed in the subtype), lower-cased |
| `toleranceSeconds` | Inbound: 1 – 3 600 |
| `schemeOptions` | Both: names ^\[a-z0-9\_\]{1,64}$, string values ≤ 255 bytes |
| `description` | Outbound: ≤ 1 000 characters |
| `name` | Both, on create and update: ≤ 191 characters |
| Imported inbound secret | 1 – 1 024 bytes, no control characters |

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/webhooks-for-laravel.md)
