---
title: "Delivery log and resources — Webhooks for Laravel | Roundly"
description: "Swappable models and their helpers, querying the log in both directions, the JSON resources for your own screens and the owner reference."
url: https://roundly-consulting.com/open-source/docs/webhooks-for-laravel/delivery-log
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)

# Delivery log and resources

Every message in both directions is logged — status, attempts, response code, duration, error code and a safe message, the matched secret slot, a capped response excerpt and whether the answer was longer — never a secret, a signature or a credential header.

## Models

| Model | Key | Table |
| --- | --- | --- |
| `InboundEndpoint` | `models.inbound_endpoint` | `webhook_inbound_endpoints` |
| `InboundDelivery` | `models.inbound_delivery` | `webhook_inbound_deliveries` |
| `OutboundEndpoint` | `models.outbound_endpoint` | `webhook_outbound_endpoints` |
| `OutboundDelivery` | `models.outbound_delivery` | `webhook_outbound_deliveries` |
| `OutboundAttempt` | `models.outbound_attempt` | `webhook_outbound_attempts` |

A replacement must extend the packaged model. Every instant is stored and read as UTC; every table has soft deletes (prune deletes for good). Relations: InboundEndpoint::owner() and deliveries(); InboundDelivery::endpoint(); OutboundEndpoint::owner() and deliveries(); OutboundDelivery::endpoint() and attemptLog(); OutboundAttempt::delivery(). The casts Casts\\UtcDateTime, Casts\\StoredPayload and Casts\\StoredText are public, for reuse on a swapped model.

| Method | Returns |
| --- | --- |
| `InboundEndpoint::url()` | The URL a sender posts to (inbound.route.url\_base + / + key, else the named route). |
| `activeSecrets()` | Both endpoint models: list<string> — the current secret, then the previous one until its grace ends. |
| `OutboundEndpoint::subscribesTo(string $eventType)` | bool — the subscription match. |
| `OutboundDelivery::webhookId()` | msg\_<public\_id> — the webhook-id receivers dedupe on, the same on every attempt and redelivery. |

## Querying the log

```php
Webhooks::inbound()->deliveries($endpoint)->where('status', 'failed')->latest('received_at')->paginate();
Webhooks::outbound()->deliveries()->where('status', 'retrying')->get();
Webhooks::outbound()->attempts($delivery);      // every attempt of every series, in order
```

## JSON resources

In RoundlyConsulting\\Webhooks\\Http\\Resources. Instants render as ISO 8601 UTC, enums as their value — never a secret, ciphertext, header value or class name:

| Resource | Fields |
| --- | --- |
| `InboundEndpointResource` | id, name, url, scheme, handler, enabled, disabled\_reason, owner, last\_received\_at, secret\_rotated\_at, previous\_secret\_expires\_at, created\_at |
| `InboundDeliveryResource` | id (public id), endpoint\_id, event\_type, status, signature\_slot, received\_at, processed\_at, failed\_at, runs, error\_code, error\_message, payload\_pruned; ->withPayload(bool $include = true) adds the sanitized payload |
| `OutboundEndpointResource` | id, name, description, url (scheme://host\[:port\]/…), host, event\_types, header names, scheme, enabled, disabled\_reason, consecutive\_failures, last\_success\_at, last\_failure\_at, owner, created\_at |
| `OutboundDeliveryResource` | id (msg\_…), endpoint\_id, event\_type, status, attempts, series, max\_attempts, next\_attempt\_at, last\_response\_status, last\_error\_code, succeeded\_at, failed\_at, created\_at |
| `OutboundAttemptResource` | number, series, manual, started\_at, duration\_ms, response\_status, response\_truncated, error\_code, error\_message, retry\_after\_seconds |

```php
use RoundlyConsulting\Webhooks\Http\Resources\InboundDeliveryResource;
use RoundlyConsulting\Webhooks\Http\Resources\InboundEndpointResource;
use RoundlyConsulting\Webhooks\Http\Resources\OutboundAttemptResource;

$endpoints = InboundEndpointResource::collection(Webhooks::for($team)->inbound()->endpoints());   // owner eager-loaded
$delivery = InboundDeliveryResource::make($inboundDelivery)->withPayload();                        // + the sanitized payload
$attempts = OutboundAttemptResource::collection(Webhooks::outbound()->attempts($outboundDelivery));
```

Return them from your own controllers. The package ships no management routes or UI — only the inbound receiving route — so the API and screens your customers use to manage their endpoints are yours, built on the sub-accessors and these resources. OutboundEndpointResource::redact(string $url) is the URL redaction on its own, for your own views.

## The owner reference

An endpoint’s owner renders only through WebhookOwnerResource — {id, type} with the class basename, so no App\\Models\\Team goes over the wire. Point resources.owner at your own JsonResource to add a name or an avatar; it receives the owner model. Eager-load owner on endpoint collections (endpoints() does) — not loaded, owner renders null.

```php
// config/webhooks.php
'resources' => ['owner' => App\Http\Resources\WebhookOwnerResource::class],
```

## Database schema

Five tables, named by webhooks.tables.\*, on MySQL and PostgreSQL. Ids follow primary\_key\_type, the owner morph follows key\_type; foreign keys restrict on delete, so a delivery keeps its endpoint row and an attempt its delivery until retention removes them in order:

| Table | Columns |
| --- | --- |
| `webhook_inbound_endpoints` | owner morph (nullable), name, url\_key (32, unique), signature\_scheme, scheme\_options, secret and previous\_secret (encrypted, hidden), previous\_secret\_expires\_at, secret\_rotated\_at, handler, the overrides (rate\_limit\_per\_minute, max\_payload\_bytes, allowed\_content\_types, tolerance\_seconds), enabled, disabled\_at, disabled\_reason, last\_received\_at. |
| `webhook_inbound_deliveries` | public\_id (ULID, unique), endpoint\_id, idempotency\_key (unique with endpoint\_id — the dedupe backstop), external\_id, event\_type, status, headers, payload (stored, hidden), payload\_bytes, payload\_digest, payload\_pruned\_at, signature\_slot, received\_at, queued\_at, processing\_started\_at, processed\_at, failed\_at, runs, attempts, duration\_ms, last\_error\_code, last\_error\_message. |
| `webhook_outbound_endpoints` | owner morph (nullable), name, description, url (encrypted, hidden), url\_host, signature\_scheme, scheme\_options, secret and previous\_secret (encrypted, hidden), event\_types, headers (encrypted, hidden), rate\_limit\_per\_minute, enabled, disabled\_at, disabled\_reason, consecutive\_failures, last\_success\_at, last\_failure\_at. |
| `webhook_outbound_deliveries` | public\_id (the ULID behind msg\_…), endpoint\_id, event\_type, body (stored, hidden), body\_bytes, body\_digest, payload\_pruned\_at, status, attempts, series, max\_attempts, next\_attempt\_at, claimed\_until, dispatch\_token, dispatched\_at, redispatch\_at, last\_attempt\_at, succeeded\_at, failed\_at, last\_response\_status, last\_duration\_ms, last\_error\_code, last\_error\_message. |
| `webhook_outbound_attempts` | delivery\_id, series and number (unique with delivery\_id), manual, started\_at, duration\_ms, response\_status, response\_body (capped excerpt), response\_truncated, retry\_after\_seconds, remote\_address, error\_code, error\_message, next\_attempt\_at. |

## 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)
