webong / proxy
Laravel endpoint registries, persistence and webhook integration with portable Proxy transports.
Requires
- php: ^8.3 || ^8.4
- guzzlehttp/guzzle: ^7.10
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- spatie/laravel-webhook-client: ^3.5
- spatie/laravel-webhook-server: ^3.10
Requires (Dev)
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-14 13:04:38 UTC
README
webong/proxy is a Laravel package for registry-backed webhook endpoints and
outbound client profiles. Version v0.3.1 provides portable PHP/Go
HTTP clients, TCP dialers and UDP associations; its wire profiles retain
version: 1, independently of the package version. An endpoint is a durable network identity: it
can receive and route webhooks through subscriptions, and it can own named
outbound profiles for clients such as Guzzle.
Install
composer require webong/proxy
Laravel discovers Webong\Proxy\WebProxyServiceProvider automatically.
Publish the package configuration and normal migrations, then migrate:
php artisan vendor:publish --tag=proxy-config php artisan vendor:publish --tag=proxy-migrations php artisan migrate
The normal migration set creates:
endpointssubscriptionsendpoint_registrationsprofilessubscription_locks(Laravel adapter transaction mutexes, unreleased)
All table names are configurable in config/proxy.php or through the matching
PROXY_*_TABLE environment variables.
Configuration
The package config is config/proxy.php and is read through the proxy.*
namespace. Common environment variables are:
PROXY_URL=https://proxy.example.test PROXY_SECRET=replace-with-an-internal-signing-secret PROXY_CHANNEL=default PROXY_REGISTRY=local PROXY_ENDPOINTS_TABLE=endpoints PROXY_SUBSCRIPTIONS_TABLE=subscriptions PROXY_ENDPOINT_REGISTRATIONS_TABLE=endpoint_registrations PROXY_PROFILES_TABLE=profiles
The built-in local registry uses the database driver and provider. Custom
registries can supply their own endpoint driver and provider through
proxy.registries and proxy.providers.
Endpoints
Create or retrieve an endpoint through EndpointRegistry. The endpoint key is
the stable public identity, while client selects the webhook router that
handles incoming work.
use Webong\Proxy\EndpointDefinition; use Webong\Proxy\EndpointRegistry; $endpoint = app(EndpointRegistry::class)->ensure(new EndpointDefinition( client: 'partner-webhooks', externalId: 'account-123', signingSecret: $partnerSigningSecret, verificationToken: $partnerVerificationToken, endpointKey: 'partner-account-123', credentialOwnerId: 'account-123', ));
An endpoint is not limited to webhook subscriptions. It is the registry-owned parent for all endpoint attachments, including outbound profiles.
Webhook subscriptions
Attach destinations when an inbound webhook route should dispatch to an HTTP request, event, or job:
use Webong\Proxy\DestinationDefinition; use Webong\Proxy\Enums\WebhookProxyTargetType; $endpoint->attach(new DestinationDefinition( ownerId: 'account-123', registrationId: 'partner-connection', webhookGroup: 'partner', routingScope: 'account', routingKey: 'account-123', target: 'https://receiver.example.test/webhooks/partner', targetType: WebhookProxyTargetType::REQUEST, ));
The package uses the configured channel and registry to resolve the endpoint,
match an incoming route, and dispatch active subscriptions. Delivery options
such as queue name, retries, backoff, idempotency TTL, and failure logging are
configured under proxy.*.
Registry-backed Guzzle profiles
Profiles belong to an endpoint and are persisted by that endpoint's registry. They are not global configuration entries. This lets each endpoint have its own egress identity and keeps profile credentials in the registry store.
use Webong\Proxy\GuzzleClientFactory; use Webong\Proxy\ProfileDefinition; $endpoint->registerProfile(new ProfileDefinition( name: 'partner-api', configuration: [ 'proxy' => [ 'https' => 'http://proxy-user:proxy-password@proxy.example.test:8080', 'no' => ['.internal.example.test'], ], 'options' => [ 'connect_timeout' => 5, 'timeout' => 15, ], ], )); $client = app(GuzzleClientFactory::class)->make($endpoint, 'partner-api'); $response = $client->get('https://api.partner.example.test/v1/status');
GuzzleClientFactory accepts Guzzle's usual proxy string or per-scheme map,
including the no bypass list. Other Guzzle options belong under options.
Profile configuration is encrypted at rest by the database provider.
Migrating from webong/web-proxy
New applications should use only the normal migration tag. Existing installations that already ran the historical prefixed migrations need one explicit compatibility step before they migrate the renamed package:
php artisan vendor:publish --tag=proxy-legacy-migrations php artisan vendor:publish --tag=proxy-migrations php artisan migrate
The opt-in legacy migration renames the historical prefixed tables and updates
Laravel's migrations table to the new migration names. It is intentionally
not included in proxy-migrations.
Testing
composer test
Composer development packaging (unreleased)
The next package candidate keeps this full Laravel integration in
webong/proxy and provides webong/proxy-transport for PHP transport-only
consumers. Registries and persistence remain here; public namespaces and the Go
module are unchanged. The new transport package is not published yet.
For local development, run php scripts/bootstrap-composer.php on a fresh clone.
The Composer Source Plugin links packages/transport as a workspace. Ordinary
published-package consumers will not need the plugin. See
package installation, bootstrap and release order before
changing dependencies; projection alone cannot remove Laravel requirements.
Portable HTTP profiles (PHP and Go)
Proxy now includes a versioned HTTP connection profile shared by both libraries.
See the profile contract for supported settings and validation.
The existing Laravel registry and webhook APIs remain in their current locations.
Existing GuzzleClientFactory::make and options preserve Guzzle-only behavior.
Use $factory->profile($endpoint, 'partner-api') to explicitly normalize an
endpoint profile for portable export; invalid portable values are rejected. The normalizer extracts connection settings;
headers, TLS options and other Guzzle-only fields are not exported.
The PHP classes under Webong\Proxy\Http do not require a Laravel application.
use Webong\Proxy\Http\HttpProfileNormalizer; use Webong\Proxy\Http\GuzzleOptions; $profile = (new HttpProfileNormalizer())->normalize([ 'proxy' => ['https' => 'http://proxy.example.test:8080'], 'options' => ['connect_timeout' => 5, 'timeout' => 15], ]); $json = $profile->toJson(); $client = new \GuzzleHttp\Client(GuzzleOptions::fromProfile($profile));
The Go module is github.com/webong/proxy:
import ( "github.com/webong/proxy/httpprofile" "github.com/webong/proxy/httpclient" ) profile, err := httpprofile.Parse(profileJSON) if err != nil { return err } client, err := httpclient.New(profile, nil) if err != nil { return err } defer client.CloseIdleConnections() // Reuse client for requests made with this profile.
httpclient.New clones an optional base *http.Transport, preserving its TLS
settings and dialer. Connect deadlines wrap supplied context-aware dialers, including TLS dialers.
The adapters round positive deadlines up to milliseconds. Custom Go dialers
must honor their context; legacy Dial and DialTLS are rejected with a connect timeout. With a nil base,
it uses package-owned standard transport defaults, independent of later changes
to http.DefaultTransport. Missing timeouts use the underlying
transport defaults and no overall request deadline. Caller-supplied clients own
redirect and destination authorization policies. No queue or registry persistence is included in the Go library.
Run the Go checks with go test -race ./... and go vet ./....
TCP dialers and SOCKS5
The v0.3.0 transport contract is frozen for its patch line. See the public
contract, adoption guide, and
release notes. Package v0.3.0 retains wire version: 1;
it does not declare a package v1 API.
Both libraries now provide direct TCP, SOCKS5 CONNECT, and HTTP/HTTPS CONNECT dialers.
For HTTP CONNECT use http://user:password@proxy.example:8080 in a TCP profile;
credentials are optional, Basic authentication is supported, and the proxy resolves
the target hostname.
Use https:// to secure the proxy hop (default port 443); certificate and
hostname verification are always enabled. Private CAs use Go's
tcpdial.WithProxyRootCAs(roots) or PHP's proxyCaFile constructor argument.
Go supports nesting target TLS inside this tunnel; the PHP native stream API
provides the proxy TLS layer only. See the TCP contract for that limitation.
socks5:// resolves destination names locally; socks5h:// resolves them at the proxy.
Both support IPv4, IPv6, and optional username/password authentication.
use Webong\Proxy\Tcp\TcpProfile; use Webong\Proxy\Tcp\TcpDialer; $profile = TcpProfile::fromArray([ 'version' => 1, 'proxy' => 'socks5h://proxy.example.test:1080', 'connect_timeout_seconds' => 5, ]); $stream = (new TcpDialer($profile))->dial('service.example.test', 9000); try { // Set application I/O timeouts and perform the application protocol here. } finally { fclose($stream); }
import "github.com/webong/proxy/tcpdial" profile := tcpdial.Profile{ Version: 1, Proxy: "socks5h://proxy.example.test:1080", ConnectTimeoutSeconds: 5, } dialer, err := tcpdial.New(profile, nil, nil) if err != nil { return err } conn, err := dialer.DialContext(ctx, "tcp", "service.example.test:9000") if err != nil { return err } defer conn.Close() // Set application deadlines and perform the application protocol here.
Go callers that previously passed socketprofile.Profile into tcpdial.New
should convert with tcpdial.Profile(sharedProfile), or construct a
tcpdial.Profile directly. TCP profiles now validate HTTP CONNECT independently
of the shared SOCKS-only UDP profile.
The HTTP profile adapters also accept SOCKS proxy URLs. PHP SOCKS HTTP requires ext-curl; mandatory authenticated SOCKS HTTP additionally requires libcurl 8.23+. The raw PHP TCP dialer supports authentication without cURL. Read the TCP contract for setup timeouts, PHP blocking DNS limitations, custom connectors/resolvers, TLS ownership, and protocol boundaries.
UDP and SOCKS5 UDP ASSOCIATE
Both libraries provide direct UDP sockets and owning SOCKS5 UDP associations. They preserve datagram boundaries, binary/empty payloads, and source addresses. The TCP control connection remains open until the association closes.
use Webong\Proxy\Udp\UdpProfile; use Webong\Proxy\Udp\UdpDialer; $profile = UdpProfile::fromArray([ 'version' => 1, 'proxy' => 'socks5h://proxy.example.test:1080', ]); $udp = (new UdpDialer($profile))->open(); try { $udp->sendTo('ping', 'service.example.test', 9000); $reply = $udp->receive(5); // payload, host, port } finally { $udp->close(); }
import "github.com/webong/proxy/udpdial" profile := udpdial.Profile{Version: 1, Proxy: "socks5h://proxy.example.test:1080"} dialer, err := udpdial.New(profile, nil, nil) if err != nil { return err } udp, err := dialer.Open(ctx, "") if err != nil { return err } defer udp.Close() udp.SetDeadline(time.Now().Add(5 * time.Second)) _, err = udp.WriteTo([]byte("ping"), udpdial.Address{Host: "service.example.test", Port: 9000}) if err != nil { return err } buffer := make([]byte, udpdial.MaxWireSize) n, source, err := udp.ReadFrom(buffer) // Handle err, buffer[:n], and source here.
Omit proxy for direct UDP. Raw UDP authentication needs no cURL extension.
The UDP contract explains DNS modes, IPv6 bindings, payload limits,
control-connection lifetime, source filtering, and unsupported SOCKS fragmentation.
SQL registry storage
Version v0.3.1 fixes SQL registry portability on SQLite, PostgreSQL, MySQL and MariaDB. Existing registries require an additive subscription-identity migration. See SQL storage and upgrade notes for supported versions, the portable subscription identity migration, partial MySQL migration recovery and rollback requirements.
Storage-neutral registry contracts (unreleased)
packages/registry provides webong/proxy-registry: plain endpoint, destination
and profile records and repository contracts, requiring only PHP. Custom apps own
their stores and schemas. This package (webong/proxy) remains the first Laravel
adapter, owning Eloquent persistence, encryption and migrations while preserving
its existing public APIs. See the webong/proxy-registry README for the storage contract.
The unreleased Laravel adapter concurrency fix also adds
2026_11_14_000001_create_subscription_locks_table. Existing Laravel installations
must publish the new proxy-migrations file and run php artisan migrate before
using the new adapter. It creates only registration mutex rows; it does not rewrite
endpoint, subscription or profile data. Configure proxy.tables.subscription_locks
(or PROXY_SUBSCRIPTION_LOCKS_TABLE) before migrating if a custom table name is
needed. The neutral registry package has no migration dependency.