PHP Luminova: HTTP Webhook Request
Securely send and receive webhook requests with built-in HMAC signature verification, origin checks, IP allowlists and blocklists, and flexible error handling for reliable third-party integrations.
The Webhook class in Luminova makes it easy to send, receive, and secure webhook requests over HTTP. It supports both outgoing requests to external services and incoming requests from trusted sources, helping you build reliable integrations and automation workflows.
What It Does
The Webhook class helps you:
- Send HTTP requests with custom headers, payloads, and request options.
- Automatically sign outgoing requests using HMAC to protect payload integrity.
- Receive and verify incoming webhook signatures to confirm the request is authentic.
- Validate request origins and manage IP allowlists or blocklists for added security.
- Handle request errors and generate proper responses when validation fails.
Usage
Sending a Webhook
Outbound webhook (
clients)Send data from your app to another service when something happens (user signup, order completed, etc.).
Using the Webhook Class
This builds and sends a signed request using HMAC. The receiver can verify it using the same secret.
use Luminova\Http\Webhook;
$webhook = (new Webhook('https://webhook.site/<WEBHOOK-TOKEN>'))
->setMethod('POST')
->setHeaders(['Accept' => 'application/json'])
->setPayload([
'status' => 'OK',
'user' => [
'id' => 42,
'email' => '[email protected]',
],
])
->setSecret('your-shared-secret') // Optional if passed to sign()
->setAlgo('sha256') // Default: sha256
->setSignatureHeaderNameName('X-Signature') // Custom signature header name
->setEvent('user.registered') // Optional event name
->sign(); // Generate signature
$response = $webhook->send();
if ($response->getStatusCode() === 200) {
echo "Webhook sent. Signature: {$webhook->getSignature()}";
} else {
echo "Failed. Status: " . $response->getStatusCode();
}Using CURL
Same request, same structure..
curl -X POST "https://example.com/webhook/users" \
-H "Accept: application/json" \
-H "X-Signature: <SIGNATURE>" \
-H "Content-Type: application/json" \
-d '{
"_data": {
"status": "OK",
"user": {
"id": 42,
"email": "[email protected]"
}
},
"_hook": {
"app.webhook.event": "user.registered",
"app.webhook.signable": 1
}
}'Structure rules:
_data→ your actual payload_hook→ metadata used for validation
Handling Incoming Webhooks
Inbound webhook (
server)Receive requests from external services (payment gateways, GitHub, Slack) and verify them before processing.
Webhook Controller
Typical HTTP controller setup for handling webhooks.
namespace App\Controllers\Http;
use Luminova\Http\Webhook;
use Luminova\Base\Controller;
use Luminova\Attributes\Route;
use Luminova\Attributes\Prefix;
use App\Errors\Controllers\ErrorController;
use function Luminova\Funcs\response;
#[Prefix(pattern: '/webhooks/(:root)', onError: [ErrorController::class, 'onApiError'])]
class WebhooksController extends Controller
{
private ?Webhook $webhook = null;
protected function onCreate(): void
{
// Listen for incoming request
$this->webhook = Webhook::listen(
/* Resolve signature internally via setSignatureHeaderName */
signature: null,
/* Manually set endpoint events specific in controller method via setAcceptEvents */
events: []
);
// Signing algorithm
$this->webhook->setAlgo('sha256');
// Verification key
$this->webhook->setSecret('your-shared-secret');
// Event field name
$this->webhook->setEventField('event');
// Optionally set blacklist
// $webhook->addIPBlacklist([...]);
}
// Handling a PayStack payment webhook.
#[Route('/webhooks/paystack/', methods: ['POST'])]
public function onPayStackPayment(): int
{
// Allowed event names
$this->webhook->setAcceptEvents(['charge.success', 'invoice.create']);
// Allow only trusted PayStack IPs
$this->webhook->addIPWhitelist([
'52.31.139.75',
'52.49.173.169',
'52.214.14.220'
]);
// Signature header
$this->webhook->setSignatureHeaderName('X-PayStack-Signature');
// Process payload
return $this->webhook->unpack(function (mixed $payload) {
// Handle payment here
return response(200)->json(['ok' => true]);
});
}
// Handling a custom users webhook.
#[Route('/webhooks/users/', methods: ['POST'])]
public function onUsers(): int
{
$this->webhook->setAcceptEvents([
'user.registered',
'user.deactivated'
]);
$this->webhook->setSignatureHeaderName('X-Signature');
// Optional security controls
$this->webhook->setAllowedOrigins(['*']);
$this->webhook->setAllowEmptyOrigin(true);
return $this->webhook->unpack(function (mixed $payload): int {
return response(200)->json(['ok' => true]);
});
}
}Sending Request via CURL
curl -X POST "https://example.com/webhook/<ENDPOINT>" \
-H "Accept: application/json" \
-H "<X_SIGNATURE_HEADER>: <SIGNATURE_VALUE>" \
-H "Content-Type: application/json" \
-d '{
"event": "charge.success",
"data": { ... }
}'Class Definition
- Class namespace:
Luminova\Http\Webhook - This class implements: \Luminova\Interface\LazyObjectInterface
Things to Know:
- Payload: The data you want to send or receive.
- Signature: A hash value used to verify that the payload was not changed during transfer.
- Secret: A key known only to your app and the other system to sign/verify requests.
- Header Name: The HTTP header used to send the signature (default:
X-Signature, but customizable).
Methods
constructor
Create a new webhook client instance.
Initializes the webhook with the target URL, request method, timeout, and default sending state.
public __construct(string $url, string $method = 'POST', int $timeout = 0): mixedParameters:
| Parameter | Type | Description |
|---|---|---|
$url | string | Target webhook endpoint URL. |
$method | string | HTTP request method (default: POST). |
$timeout | int | Request timeout in seconds (default: 0 no timeout). |
listen
Create a webhook listener for incoming request verification.
This method creates a new webhook instance in listening mode and prepares it to validate incoming payloads, signatures, events, origins, and client IPs.
If no $signature is passed and no signature header name is configured, signature verification will be skipped. To resolve the signature automatically from the request headers, call setSignatureHeaderName().
public static listen(?string $signature = null, array|string $events = []): staticParameters:
| Parameter | Type | Description |
|---|---|---|
$signature | string|null | Optional request signature (for example, from the X-Signature header). |
$events | array|string | Optional event name or list of allowed events to accept. |
Return Value:
\T<Luminova\Http\Webhook> - Returns a new webhook listener instance.
Example:
Create and verify an incoming webhook:
use Luminova\Http\Webhook;
// Same as using $_SERVER['HTTP_X_SIGNATURE']
$signature = $request->header->get('X-Signature');
$webhook = Webhook::listen($signature)
->setAcceptEvents('event-name')
->setAlgo('sha256')
->setSecret('your-secret-key');
$webhook->unpack(fn (mixed $data) => store($data));See Also:
unpack()- Verify and process the incoming request.setEventField()- Set the payload field used for the event name.setSignatureHeaderName()- Set the request header used for the signature.setAllowedOrigins()- Restrict allowed request origins.
request
Create a webhook client for sending requests.
This is a shortcut factory for creating a new webhook instance with the target URL, HTTP method, and request timeout.
If no $signature is passed and no signature header name is configured, signature verification will be skipped. To resolve the signature automatically from the request headers, call setSignatureHeaderName().
public static request(string $url, string $method = 'POST', int $timeout = 0): staticParameters:
| Parameter | Type | Description |
|---|---|---|
$url | string | Target webhook endpoint URL. |
$method | string | HTTP request method to use (default: POST). |
$timeout | int | Request timeout in seconds. Use 0 for no timeout. |
Return Value:
\T<Luminova\Http\Webhook> - Returns a new webhook instance for request client.
Throws:
- \Luminova\Exceptions\InvalidArgumentException - If the URL is empty or invalid.
Example:
Create a basic webhook client:
use Luminova\Http\Webhook;
$webhook = Webhook::request('https://example.com/webhook')
->setPayload(['status' => 'OK'])
->send();unpack
Verify and unpack an incoming webhook request.
This method validates the request origin, IP rules, listener state, and payload signature before returning the decoded data.
If validation succeeds:
- returns the decoded payload, or
- passes it to the callback and returns the callback result.
If validation fails:
- throws an exception in development, or
- sends an error response and terminates request in production.
public unpack(?Closure $onResponse = null, ?array $data = null): mixedParameters:
| Parameter | Type | Description |
|---|---|---|
$onResponse | (Closure(mixed $data):mixed)|null | Optional callback to process the verified payload. |
$data | array|null | Optional payload to verify instead of the current request body (default: HTTP request body). |
Return Value:
mixed - Returns the decoded payload, callback result, or false if terminated in production.
Throws:
- \Luminova\Exceptions\RuntimeException - If verification fails in development mode.
Example:
use Luminova\Http\Webhook;
$webhook = Webhook::listen(...);
$webhook->unpack(function(mixed $payload): bool {
// Handle verified data
file_put_contents('path/to/hooks/data.txt', var_export($payload, true));
return true;
});
// Get verified payload directly
$data = $webhook->unpack();Additionally:
You can also pass HTTP request body or array to decode
(new Request)->getParsedBody(),instead of detecting internally.This can be useful for debugging webhook request.
sign
Enable HMAC signing for the webhook payload.
This method marks the payload as signable. When the webhook is sent, an HMAC signaturewill be generated and included in the request headers under the defined signature header.
public sign(?string $secret = null): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$secret | string|null | Optional secret key for signing. If omitted, the value from setSecret() is used if available. |
Return Value:
Luminova\Http\Webhook - Return instance of webhook class.
Example:
$webhook->setPayload(['event' => 'update'])
->sign()
->setAlgo('sha256')
->setSignatureHeaderName('X-Foo-Signature');
// Get the signature from request header
// $request->header->get('X-Foo-Signature')Note:
If no secret is provided and none was set previously, a random secret will be generated.You can retrieve the key used by calling
getSecret()after sending.
send
Send the configured webhook request.
This method builds the request, optionally signs the payload, attaches it using the selected request field, and sends itto the target webhook URL.
Available Fields:
query→ For GET, append payload as URL query parameters.body→ Send raw request body (for JSON or raw data).form_params→ Send URL-encoded form data (application/x-www-form-urlencoded).multipart→ Send multipart form data (multipart/form-data).
If no field is provided:
- GET requests use
query - all other methods use
body
public send(?string $field = null): Luminova\Interface\ResponseInterfaceParameters:
| Parameter | Type | Description |
|---|---|---|
$field | string|null | Optional request field used to attach the payload. |
Return Value:
Luminova\Interface\ResponseInterface<\Psr\Http\Message\ResponseInterface>
- Returns the HTTP response from the remote server.
Throws:
- \Luminova\Exceptions\EncryptionException - If payload signing fails.
- \Luminova\Exceptions\RequestException - IIf the webhook URL is missing or the request fails.
- \Luminova\Exceptions\InvalidArgumentException - If an unsupported request field is provided.
Examples:
Send a signed POST webhook:
$webhook = Webhook::request('https://example.com/webhook/<ENDPOINT>', 'POST')
->setPayload([
'id' => 12345,
'message' => 'Hello world',
])
->setSecret('your-shared-secret')
->setAlgo('sha256')
->setSignatureHeaderName('X-Signature')
->setEvent('foo.bar')
->sign();
$response = $webhook->send();
if ($response->getStatusCode() === 200) {
echo "Webhook sent successfully.";
}Send GET request with query parameters:
$response = $webhook
->setMethod('GET')
->setPayload(['id' => 123])
->send('query');free
Free request payload data.
public free(): voidisSignature
Verify the webhook payload against its HMAC signature.
This method validates the incoming request using the provided or incoming detected payloadand compares the HMAC digest to the signature. If an event name is provided in the context, it is also validated against the payload.
public isSignature(?array $data = null): boolParameters:
| Parameter | Type | Description |
|---|---|---|
$data | array|null | Optional payload to validate (defaults to request body). |
Return Value:
bool - Return true if the payload and signature match; false otherwise.
isWhitelisted
Check if the current request IP is whitelisted.
public isWhitelisted(): boolReturn Value:
bool - Returns true if the request IP matches a whitelist, false otherwise.
isBlacklisted
Check if the current request IP is blacklisted.
public isBlacklisted(): boolReturn Value:
bool - Returns true if the request IP matches a blacklist, false otherwise.
isOriginAllowed
Check whether the current request origin is allowed.
If no allowed origins are configured, all requests are accepted.Same-origin requests are always allowed. For cross-origin requests,the request origin must match a configured allowed origin.
public isOriginAllowed(?string &$allowed = null): boolParameters:
| Parameter | Type | Description |
|---|---|---|
&$allowed | string|null | Matched allowed origin passed by reference, if found. |
Return Value:
bool - Returns true if the request origin is allowed, otherwise false.
getPayload
Retrieve the webhook payload.
This method returns either the full payload if validation succeeded otherwise
public getPayload(): mixedReturn Value:
array|string|null - Return decoded payload or empty if failed.
getSignature
Get the HMAC signature generated for the payload.
public getSignature(): ?stringReturn Value:
string|null - Returns the signature string, or null if not generated.
getSecret
Get the secret key used for signing or verifying the signature.
public getSecret(): ?stringReturn Value:
string|null - Returns the secret key, or null if not set.
setMethod
Set the request HTTP method for sending webhook request.
public setMethod(string $method): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$method | string | The HTTP method (e.g., GET, POST, PUT, DELETE). |
Return Value:
self - Return instance of webhook class.
setTimeout
Set the HTTP request client timeout in seconds.
public setTimeout(int $seconds): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$seconds | int | Number of seconds before the request times out. |
Return Value:
Luminova\Http\Webhook - Return instance of webhook class.
Example:
$webhook->setTimeout(30); // 30 second timeoutsetHeaders
Set custom headers to send with the request.
public setHeaders(array<string,string> $headers): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$headers | array<string,string> | Associative array of headers. |
Return Value:
Luminova\Http\Webhook - Return instance of webhook class.
Example:
$webhook->setHeaders([
'X-App-Token' => 'abc123',
'Accept' => 'application/json'
]);setPayload
Set the webhook request payload.
The sets the HTTP webhook request body to be sign and send.
public setPayload(array|string $payload): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$payload | array|string | Payload can be an array or a raw JSON string. |
Return Value:
self - Return instance of webhook class.
Example:
$webhook->setPayload(['order' => 'order.id']);
// Or String
$webhook->setPayload('order.id');
// Or String Json
$webhook->setPayload('{"order": "order.id"}');setSecret
Set payload signature secrete.
The secret key is used for signing outgoing and verifying incoming request payload.
public setSecret(string $secret): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$secret | string | The signature sign secret key (default: env('app.key')). |
Return Value:
self - Return instance of webhook class.
Return Value:
self - Return instance of webhook class.
setEvent
Define the outgoing webhook event name.
This sets the event identifier that will be sent in the payload under the internal _hook structure using the predefined EVENT_KEY.
Has no effect when the instance is in listening mode.
public setEvent(string $name): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$name | string | The event name to assign (e.g., 'user.created', 'payment.failed'). |
Return Value:
self - Return instance of webhook class.
setAlgo
Set the HMAC hashing algorithm used for signing the webhook payload.
This defines the algorithm used in signature generation and verification (e.g., sha256, sha512).
public setAlgo(string $algo): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$algo | string | The name of the hashing algorithm (default is 'sha256'). |
Return Value:
self - Return instance of webhook class.
setSignatureHeaderName
Define the header name where the HMAC signature will be attached.
This sets the HTTP header used to transmit the payload signature (e.g., X-Signature, X-MyApp-Signature).
public setSignatureHeaderName(string $xSignature): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$xSignature | string | The custom header name for the signature (default: X-Signature). |
Return Value:
self - Return instance of webhook class.
setAcceptEvents
Define accepted incoming webhook event names.
This registers the events that this listener will accept when processingincoming webhook requests.
Only applies when the instance is in listening mode.
public setAcceptEvents(string[]|string $name): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$name | array|string | Event name(s) to accept (e.g., 'user.created', 'payment.failed'). |
Return Value:
self - Return instance of webhook class.
setEventField
Define the event key(s) used to extract and resolve incoming the webhook event name.
Some webhook providers (e.g. PayStack) include the event name undera custom payload key such as event instead of the default _hook structure.
This method allows you to register a key that should be usedto resolve the incoming event name.
public setEventField(string $name): staticParameters:
| Parameter | Type | Description |
|---|---|---|
$name | string | The payload key that contains the event name. |
Return Value:
self - Return instance of webhook class.
setAllowedOrigins
Define allowed origins for incoming webhook requests.
This restricts which request origins are permitted when the webhookis operating in listening mode. Multiple origins can be added eitheras a single string or an array of values.
Has no effect when the instance is not in listening mode.
public setAllowedOrigins(string|string[] $origin = ['*']): staticParameters:
| Parameter | Type | Description |
|---|---|---|
$origin | string|string[] | One or more allowed origin URLs or domains. |
Return Value:
self - Return instance of webhook class.
setAllowEmptyOrigin
Allow or deny requests without an Origin header.
When enabled, requests that do not include an Origin header (e.g. server-to-server or CLI requests) will be accepted. When disabled, such requests will be rejected.
public setAllowEmptyOrigin(bool $allow = true): staticParameters:
| Parameter | Type | Description |
|---|---|---|
$allow | bool | Whether to allow requests with no Origin header. |
Return Value:
self - Return instance of webhook class.
addPayload
Adds a key-value pair to the webhook payload.
public addPayload(string $name, mixed $value): selfParameters:
| Parameter | Type | Description |
|---|---|---|
$name | string | The key to add to the payload. |
$value | mixed | The value to assign to the given key. |
Return Value:
Luminova\Http\Webhook - Return instance of webhook class.
Example:
$webhook->addPayload('event', 'user.created');addIPBlacklist
Add one or more IP addresses to the blacklist.
Requests originating from these IPs will be denied during validation.
public addIPBlacklist(string|string[] $ip): staticParameters:
| Parameter | Type | Description |
|---|---|---|
$ip | string|string[] | IP address or list of addresses to block. |
Return Value:
self - Return instance of webhook class.
addIPWhitelist
Add one or more IP addresses to the whitelist.
When defined, only requests from these IPs will be accepted.
public addIPWhitelist(string|string[] $ip): staticParameters:
| Parameter | Type | Description |
|---|---|---|
$ip | string|string[] | IP address or list of allowed addresses. |
Return Value:
self - Return instance of webhook class.