Webhook Receiver¶
Reference for the inbound webhook endpoint introduced in CFX-48. External e-commerce platforms send event notifications to this endpoint. Conflux validates the request signature, persists the call, then dispatches a queued job for platform-specific processing.
The receiver is built on spatie/laravel-webhook-client. Conflux supplies a custom signature validator (per-connection secret), a custom response, and a UUID-keyed webhook-call model; the package provides the controller, processing pipeline, and persistent buffer.
Endpoint¶
| Segment | Type | Description |
|---|---|---|
connection |
UUID | The id of the target SystemConnection |
The route is defined in routes/api.php with ->withoutMiddleware(['auth:connection', 'ensure.connection.active']). There is no connection-guard authentication or EnsureSystemConnectionIsActive check. Authentication is handled exclusively via HMAC signature validation.
Route name: webhook-client-default — set by the package convention and required so the matching WebhookConfig (config/webhook-client.php) can be resolved from the current route name. It must not be renamed.
Authentication: HMAC-SHA256 Signature¶
Every request must include a Signature header containing the hex-encoded HMAC-SHA256 hash of the raw request body, using the connection's webhook_secret as the key.
Computing the signature¶
The webhook_secret is stored in the credentials encrypted JSON field of the SystemConnection model, under the key webhook_secret.
Example (PHP)¶
Example (shell)¶
Request¶
- Content-Type:
application/json - Body: The raw JSON payload from the external platform. The structure is platform-specific and is passed through to the integration unchanged.
Required headers¶
| Header | Description |
|---|---|
Signature |
Hex-encoded HMAC-SHA256 of the raw request body |
Response¶
200 OK¶
Returned when the signature is valid, the call has been stored, and the job has been dispatched. The body is the package default, {"message":"ok"}.
The 200 response confirms that the payload has been accepted for asynchronous processing. It does not mean processing has completed — the payload is handled by ProcessWebhookJob via the queue.
Error responses¶
All error responses use the application/problem+json format defined in API Errors.
| Status | Condition | Detail |
|---|---|---|
| 403 | Signature header is missing/invalid, or the connection has no webhook_secret configured |
The webhook signature is invalid. |
| 404 | No active SystemConnection with the given UUID exists (including soft-deleted or inactive connections) |
The requested resource was not found. |
Signature failures funnel through the package's InvalidWebhookSignature exception, so a missing secret and a bad signature are intentionally indistinguishable to the caller. The 404 is produced by the connection route-model binding (see AppServiceProvider) before the receiver runs.
Processing pipeline¶
- Route-model binding (
AppServiceProvider) resolves{connection}viaSystemConnection::active()->findOrFail(). Unknown, inactive, or soft-deleted connections produce a404before any package code runs. ConnectionSignatureValidatorvalidates theSignatureheader against the resolved connection'swebhook_secret(HMAC-SHA256,hash_equals). Failure throwsInvalidWebhookSignature→403.- The package's
WebhookProcessorstores the call as aWebhookCallrow, dispatchesProcessWebhookJob, and responds with the package default200 OK({"message":"ok"}). ProcessWebhookJob(extends the package job, receives the persistedWebhookCall) resolves the originatingSystemConnectionfromconnection_id, looks up the integration viaIntegrationRegistry, and callshandleWebhook()with the stored payload. If the connection or itsSystemno longer exists, the call is logged and discarded.AbstractIntegration::handleWebhook()(sealed asfinal) wrapsprocessWebhook()with error handling. AnyThrowablethat is not anIntegrationRequestExceptionis caught and re-thrown asInvalidIntegrationResponseException.processWebhook()is the method concrete integrations override to implement platform-specific webhook processing. The default implementation throwsBadMethodCallException.
Persistence & retention¶
Every accepted call is stored in the webhook_calls table (UUID primary key, plus a connection_id linking back to the SystemConnection) for auditing and replay. This is a transient buffer: it is not soft-deleted and is mass-pruned after webhook-client.delete_after_days (30 days) via the scheduled model:prune command (routes/console.php).
Design decisions¶
Two deliberate choices deviate from defaults and are documented here for traceability:
webhook_callshas no soft deletes. The project default is to soft-delete most tables (Architecture → Soft Deletes). The webhook-call buffer is a sanctioned exception: it is transient, append-only audit/replay data that is hard-pruned on a schedule, sosoftDeletes()is intentionally omitted.- A missing secret returns the same
403as a bad signature. When a connection has nowebhook_secret,ConnectionSignatureValidatorreturnsfalserather than raising a distinct error, so the response is the genericThe webhook signature is invalid.This keeps all signature-related failures behind a singleInvalidWebhookSignaturepath and avoids leaking connection configuration state to unauthenticated callers.
Source files¶
| File | Purpose |
|---|---|
routes/api.php |
Route definition (webhook-client-default) pointing at the package controller |
config/webhook-client.php |
Receiver config: custom validator, response, model, and job |
app/Providers/AppServiceProvider.php |
connection route-model binding (404 for unknown/inactive connections) |
app/Services/Webhooks/ConnectionSignatureValidator.php |
HMAC validation against the per-connection webhook_secret |
app/Models/WebhookCall.php |
UUID-keyed webhook-call buffer model (connection_id) |
database/migrations/*_create_webhook_calls_table.php |
webhook_calls schema |
bootstrap/app.php |
InvalidWebhookSignature → 403 exception handler |
app/Jobs/ProcessWebhookJob.php |
Queued job: routes the stored payload through IntegrationRegistry to the correct integration |
app/Services/AbstractIntegration.php |
handleWebhook() (final) and processWebhook() (overridable) |
app/Contracts/IntegrationContract.php |
handleWebhook() contract method |
See also¶
- Integration Contract reference —
handleWebhook()andprocessWebhook()method signatures - System Connections — model attributes, including
credentialsandstatus - API Errors — RFC 9457 error format and handled exception types
- Implement a System Integration — step-by-step guide including webhook handling