Skip to main content

config-webhook

Configure Webhook Subscription​

Before receiving webhooks, you need to register your webhook URL.

Endpoint​

POST /apps/api/webhooks

Headers:

Authorization: Bearer {access_token}
Content-Type: application/json

Request Body​

{
"webhookUrl": "https://yourdomain.com/webhooks/wepay"
}

Field Descriptions​

FieldTypeRequired?Description
webhookUrlStringYesYour HTTPS endpoint URL to receive webhook notifications. Must be publicly accessible. Example: https://api.yoursite.com/webhooks

Example Request (cURL)​

curl -X POST "https://api.wepay.com.sa/apps/api/webhooks" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://yourdomain.com/webhooks/wepay"
}'

Example Response​

{
"data": {
"id": "019bb692-28e0-7ae1-926f-59b12c3b784c",
"webhookUrl": "https://yourdomain.com/webhooks/wepay",
"secretKey": "whsec_abc123xyz789defghijklmnopqrstuvwxyz",
"isActive": true,
"consecutiveFailures": 0,
"lastSuccessAt": null,
"lastFailureAt": null,
"createdAt": "2026-01-19T12:00:00Z"
},
"message": "Webhook created successfully.",
"status": 200,
"validationErrors": []
}

Response Fields​

FieldDescription
idYour business entity ID (used as webhook subscription identifier)
webhookUrlYour registered webhook endpoint
secretKeySecret key for signature verification (Save this securely)
isActiveWhether the webhook is currently active (false if disabled due to failures)
consecutiveFailuresNumber of consecutive delivery failures (resets to 0 on success)
lastSuccessAtTimestamp of last successful delivery
lastFailureAtTimestamp of last failed delivery
createdAtWhen the business entity was created

Important: Save the secretKey securely. You'll need it to verify webhook signatures.

The secret is only shown once when creating or regenerating. Never expose your secret key in client-side code.


Verifying Webhook Signatures​

Verify the webhook signature before processing any webhook.

Signature Format​

X-WePay-Signature contains:

sha256={hex_signature}

Verification Process​

  1. Extract the signature from X-WePay-Signature.
  2. Read the raw JSON request body.
  3. Compute HMAC-SHA256 using your webhook secret key.
  4. Compare the computed signature with the received signature using constant-time comparison.

Node.js Example​

const crypto = require("crypto")

function verifyWebhookSignature(payload, signature, secretKey) {
const expectedSignature =
"sha256=" +
crypto.createHmac("sha256", secretKey).update(payload, "utf8").digest("hex")

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature),
)
}

app.post("/webhooks/wepay", (req, res) => {
const signature = req.headers["x-wepay-signature"]
const payload = JSON.stringify(req.body)

if (!verifyWebhookSignature(payload, signature, YOUR_SECRET_KEY)) {
return res.status(401).send("Invalid signature")
}

const event = req.body

switch (event.event) {
case "payment.completed":
// handle payment
break
case "contract.released":
// handle release
break
case "refund.full-initiated":
case "refund.full-succeeded":
// handle refund events
break
}

res.status(200).send("OK")
})

PHP Example​

<?php
function verifyWebhookSignature($payload, $signature, $secretKey) {
$expectedSignature = 'sha256=' . hash_hmac('sha256', $payload, $secretKey);
return hash_equals($expectedSignature, $signature);
}

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEPAY_SIGNATURE'] ?? '';

if (!verifyWebhookSignature($payload, $signature, YOUR_SECRET_KEY)) {
http_response_code(401);
exit('Invalid signature');
}

$event = json_decode($payload, true);

switch ($event['event']) {
case 'payment.completed':
break;
case 'contract.released':
break;
case 'refund.full-initiated':
case 'refund.full-succeeded':
break;
}

http_response_code(200);
echo 'OK';
?>

Python Example​

import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
SECRET_KEY = 'your_webhook_secret_key'

def verify_signature(payload, signature, secret):
expected = 'sha256=' + hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)

@app.route('/webhooks/wepay', methods=['POST'])
def webhook_handler():
signature = request.headers.get('X-WePay-Signature', '')
payload = request.get_data(as_text=True)

if not verify_signature(payload, signature, SECRET_KEY):
abort(401)

event = request.get_json()

if event['event'] == 'payment.completed':
pass
elif event['event'] == 'contract.released':
pass
elif event['event'] in [
'refund.full-initiated',
'refund.full-succeeded'
]:
pass

return 'OK', 200

C# Example​

using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

public class WebhookController : ControllerBase
{
private readonly string _secretKey = "your_webhook_secret_key";

private bool VerifyWebhookSignature(string payload, string signature)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_secretKey));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
var expectedSignature = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();

return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(signature),
Encoding.UTF8.GetBytes(expectedSignature)
);
}

[HttpPost("webhooks/wepay")]
public async Task<IActionResult> HandleWebhook()
{
var signature = Request.Headers["X-WePay-Signature"].ToString();

using var reader = new StreamReader(Request.Body);
var payload = await reader.ReadToEndAsync();

if (!VerifyWebhookSignature(payload, signature))
{
return Unauthorized();
}

var webhookEvent = JsonSerializer.Deserialize<WebhookEvent>(payload);

switch (webhookEvent?.Event)
{
case "payment.completed":
break;
case "contract.released":
break;
case "refund.full-initiated":
case "refund.full-succeeded":
break;
}

return Ok();
}
}
important

Always use constant-time comparison (timingSafeEqual, hash_equals, compare_digest, FixedTimeEquals) to prevent timing attacks.


Get Webhook Subscription​

Retrieve your current webhook configuration.

Endpoint​

GET /apps/api/webhooks

Headers:

Authorization: Bearer {access_token}

Example Request (cURL)​

curl -X GET "https://api.wepay.com.sa/apps/api/webhooks" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Example Response​

{
"data": {
"id": "019bb692-28e0-7ae1-926f-59b12c3b784c",
"webhookUrl": "https://yourdomain.com/webhooks/wepay",
"secretKey": "whsec_abc123xyz789defghijklmnopqrstuvwxyz",
"isActive": true,
"consecutiveFailures": 0,
"lastSuccessAt": "2026-01-19T14:30:00Z",
"lastFailureAt": null,
"createdAt": "2026-01-19T12:00:00Z"
},
"message": "",
"status": 200,
"validationErrors": []
}

Update Webhook URL​

Update your existing webhook subscription with a new URL.

Endpoint​

POST /apps/api/webhooks

Headers:

Authorization: Bearer {access_token}
Content-Type: application/json

Request Body​

{
"webhookUrl": "https://newdomain.com/webhooks/wepay"
}

Example Request (cURL)​

curl -X POST "https://api.wepay.com.sa/apps/api/webhooks" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhookUrl": "https://newdomain.com/webhooks/wepay"
}'

Example Response​

{
"data": {
"id": "019bb692-28e0-7ae1-926f-59b12c3b784c",
"webhookUrl": "https://newdomain.com/webhooks/wepay",
"secretKey": "whsec_abc123xyz789defghijklmnopqrstuvwxyz",
"isActive": true,
"consecutiveFailures": 0,
"lastSuccessAt": "2026-01-19T14:30:00Z",
"lastFailureAt": null,
"createdAt": "2026-01-19T12:00:00Z"
},
"message": "Webhook updated successfully.",
"status": 200,
"validationErrors": []
}

Note: Updating the webhook URL also re-enables the subscription if it was disabled due to consecutive failures.


Regenerate Secret Key​

If your secret key is compromised, regenerate it immediately.

Endpoint​

POST /apps/api/webhooks/regenerate-secret

Headers:

Authorization: Bearer {access_token}

Example Request (cURL)​

curl -X POST "https://api.wepay.com.sa/apps/api/webhooks/regenerate-secret" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Example Response​

{
"data": {
"secretKey": "whsec_xYz123AbCdEfGhIjKlMnOpQrStUvWxYz456"
},
"message": "Webhook secret regenerated successfully.",
"status": 200,
"validationErrors": []
}

Important: After regenerating, update your server immediately to use the new secret key. Webhooks signed with the old key will fail verification.


Delete Webhook Subscription​

Remove your webhook subscription to stop receiving notifications.

Endpoint​

DELETE /apps/api/webhooks

Example Request​

curl -X DELETE "{baseUrl}/apps/api/webhooks" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Example Response​

{
"data": true,
"message": "Webhook deleted successfully.",
"status": 200,
"validationErrors": []
}