Before Alaaqat sends any event to your server, it has to be sure the URL really belongs to you. That is what validation does: Alaaqat sends a challenge to your URL, and you answer with a signature that only the holder of the webhook secret key can produce.
A webhook must be validated before it can be activated, and it is reset to Unvalidated every time you change its URL.
When you press Validate on the webhook card (or call the validate endpoint), Alaaqat sends a GET request to your webhook URL with two query parameters:
GET /your-endpoint?payload=Xk3aQ×tamp=1756600000 HTTP/1.1
Host: yourdomain.com
| Parameter | Description |
|---|---|
payload |
A random 5 character string, different every time. |
timestamp |
The Unix timestamp (seconds) of the challenge. |
Your endpoint has 10 seconds to answer. A slower reply is treated exactly like a wrong one, and the webhook stays unvalidated.
Concatenate payload and timestamp in that order, with nothing in between, sign the result with HMAC SHA-256 using your webhook secret key, and return the lowercase hexadecimal digest as the whole response body:
signature = hmac_sha256(payload + timestamp, secret)
The response must:
2xx status code,The body is compared to the expected signature character by character, so a single extra newline fails the check.
The secret key is the one shown on the webhook card in Settings > Integrations > Webhooks. Read it from your own configuration — never hard-code it in a public repository.
$secret = getenv('ALAAQAT_WEBHOOK_SECRET');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$signature = hash_hmac('sha256', $_GET['payload'].$_GET['timestamp'], $secret);
header('Content-Type: text/plain');
echo $signature; // no newline
exit;
}
Route::get('/alaaqat-webhook', function (Request $request) {
$signature = hash_hmac(
'sha256',
$request->query('payload').$request->query('timestamp'),
config('services.alaaqat.webhook_secret')
);
return response($signature)->header('Content-Type', 'text/plain');
});
const crypto = require('crypto');
app.get('/alaaqat-webhook', (req, res) => {
const signature = crypto
.createHmac('sha256', process.env.ALAAQAT_WEBHOOK_SECRET)
.update(req.query.payload + req.query.timestamp)
.digest('hex');
res.type('text/plain').send(signature);
});
import hashlib
import hmac
import os
@app.get('/alaaqat-webhook')
def validate():
message = request.args['payload'] + request.args['timestamp']
signature = hmac.new(
os.environ['ALAAQAT_WEBHOOK_SECRET'].encode(),
message.encode(),
hashlib.sha256,
).hexdigest()
return signature, 200, {'Content-Type': 'text/plain'}
The same URL receives both the validation challenge and the event deliveries, so branch on the HTTP method:
GET → a validation challenge; answer with the signature.POST → an event delivery; answer with any 2xx.The webhook becomes Validated and the Activate button becomes available. Activate it and the events you subscribed to start arriving.
Alaaqat answers with 403 and the message:
{
"message": "Unable to validate the webhook. Please ensure that the secret key is correct and the webhook endpoint is properly configured."
}
Check the following:
localhost and private addresses will not work. Use a tunnelling tool while developing.200, not a redirect to another page.{"signature": "..."}, or an HTML layout wrapped around it.payload immediately followed by timestamp, using the secret key of this webhook.