Webhooks API
Webhooks API
Manage webhook subscriptions for your app. Webhooks allow your app to receive real-time HTTP POST notifications when events occur in a store (orders, products, customers, billing, etc.).
For details on webhook payload format, signature verification, and best practices, see the Webhooks guide.
Valid Event Topics
The following event topics are available for subscription:
| Topic | Description |
|---|---|
order.created | A new order is placed |
order.updated | An order is modified |
order.status_changed | An order's status transitions (e.g. pending to confirmed) |
product.created | A new product is added |
product.updated | A product is modified |
product.deleted | A product is removed |
customer.created | A new customer account is created |
customer.updated | A customer record is modified |
app.installed | Your app is installed on a store |
app.uninstalled | Your app is uninstalled from a store |
inventory.updated | Stock levels change for a product variant |
charge.created | A billing charge is created |
charge.activated | A charge is approved and payment collected |
charge.declined | A charge is declined by the merchant |
charge.cancelled | A charge is cancelled |
charge.expired | A charge expired without merchant action |
charge.payment_failed | Payment collection failed for a charge |
subscription.renewal_pending | A recurring subscription renewal is upcoming |
customers/data_request | GDPR: Customer requests personal data export |
customers/redact | GDPR: Customer requests data deletion |
shop/redact | GDPR: Store deletion, all data to be redacted |
:::note Billing and GDPR topics
The billing/subscription topics (charge.*, subscription.*) and GDPR compliance topics (customers/*, shop/*) are only available for app-created webhook subscriptions via this API. Merchant-created webhooks from the dashboard do not support these topics.
:::
List Webhook Subscriptions
Retrieve all webhook subscriptions for your app on the current store.
GET /api/apps/v1/webhooks
Authentication
Required. Must include valid app credentials (via Bearer token or client credentials).
Scope
No specific scope required beyond app authentication.
Query Parameters
None.
Example Request
curl -X GET "https://api.selorax.io/api/apps/v1/webhooks" \
-H "Authorization: Bearer <token>"Or using client credentials:
curl -X GET "https://api.selorax.io/api/apps/v1/webhooks" \
-H "X-Client-Id: sx_app_..." \
-H "X-Client-Secret: sx_secret_..." \
-H "X-Store-Id: 22"Response
{
"data": [
{
"subscription_id": 15,
"event_topic": "order.created",
"target_url": "https://myapp.com/webhooks/orders",
"is_active": true,
"failure_count": 0,
"last_failure_at": null,
"last_success_at": "2025-06-15T14:30:00.000Z",
"created_at": "2025-06-01T00:00:00.000Z"
},
{
"subscription_id": 16,
"event_topic": "order.status_changed",
"target_url": "https://myapp.com/webhooks/orders",
"is_active": true,
"failure_count": 2,
"last_failure_at": "2025-06-14T10:00:00.000Z",
"last_success_at": "2025-06-15T14:30:00.000Z",
"created_at": "2025-06-01T00:00:00.000Z"
},
{
"subscription_id": 17,
"event_topic": "product.updated",
"target_url": "https://myapp.com/webhooks/products",
"is_active": true,
"failure_count": 0,
"last_failure_at": null,
"last_success_at": null,
"created_at": "2025-06-05T12:00:00.000Z"
}
],
"status": 200
}Response Fields
| Field | Type | Description |
|---|---|---|
subscription_id | integer | Unique subscription identifier |
event_topic | string | The event topic this subscription listens for |
target_url | string | The URL that receives POST requests when events fire |
is_active | boolean | Whether the subscription is currently active |
failure_count | integer | Number of consecutive delivery failures |
last_failure_at | string or null | Timestamp of the most recent delivery failure |
last_success_at | string or null | Timestamp of the most recent successful delivery |
created_at | string (ISO 8601) | When the subscription was created |
Create Webhook Subscription
Subscribe to an event topic. The response includes a signing_secret that is shown only once -- store it securely for signature verification.
POST /api/apps/v1/webhooks
Authentication
Required. Must include valid app credentials.
Scope
No specific scope required beyond app authentication.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
event_topic | string | Yes | The event topic to subscribe to (must be a valid topic from the list above) |
target_url | string | Yes | The HTTPS URL that will receive webhook POST requests |
Example Request
curl -X POST "https://api.selorax.io/api/apps/v1/webhooks" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"event_topic": "order.created",
"target_url": "https://myapp.com/webhooks/orders"
}'Response
{
"message": "Webhook subscription created successfully",
"data": {
"subscription_id": 18,
"event_topic": "order.created",
"target_url": "https://myapp.com/webhooks/orders",
"is_active": true,
"signing_secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"failure_count": 0,
"last_failure_at": null,
"last_success_at": null,
"created_at": "2025-06-15T15:00:00.000Z"
},
"status": 200
}:::warning Store the signing secret
The signing_secret (prefixed with whsec_) is returned only once at creation time. It will not appear in subsequent GET requests. Store it securely -- you need it to verify webhook signatures.
:::
Signing Secret
The signing_secret is used to verify that incoming webhook requests are authentically from the SeloraX platform. See Receiving Webhooks for implementation details.
Error Responses
| Code | Status | Meaning |
|---|---|---|
invalid_token | 401 | Token is expired or invalid |
missing_auth | 401 | No authentication credentials provided |
If an invalid event_topic is provided:
{
"message": "Invalid event topic. Must be one of: order.created, order.updated, ...",
"status": 400
}If the target_url is missing:
{
"message": "Missing required fields: event_topic, target_url",
"status": 400
}Delete Webhook Subscription
Remove a webhook subscription. This performs a soft-delete -- the subscription is deactivated and will no longer receive events.
DELETE /api/apps/v1/webhooks/:subscription_id
Authentication
Required. Must include valid app credentials.
Scope
No specific scope required beyond app authentication.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
subscription_id | integer | The subscription ID to delete |
Example Request
curl -X DELETE "https://api.selorax.io/api/apps/v1/webhooks/18" \
-H "Authorization: Bearer <token>"Response
{
"message": "Webhook subscription deleted.",
"status": 200
}Error Responses
| Code | Status | Meaning |
|---|---|---|
invalid_token | 401 | Token is expired or invalid |
missing_auth | 401 | No authentication credentials provided |
If the subscription_id does not exist or does not belong to your app:
{
"message": "Webhook subscription not found",
"status": 404
}Rotate Signing Secret
Generate a new signing secret for an existing webhook subscription. The old secret is immediately invalidated.
POST /api/apps/v1/webhooks/:subscription_id/rotate-secret
Authentication
Required. Must include valid app credentials.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
subscription_id | integer | The subscription ID to rotate the secret for |
Example Request
curl -X POST "https://api.selorax.io/api/apps/v1/webhooks/18/rotate-secret" \
-H "Authorization: Bearer <token>"Response
{
"message": "Signing secret rotated. Save the new signing_secret — it will not be shown again.",
"data": {
"subscription_id": 18,
"signing_secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
},
"status": 200
}:::warning
The new signing_secret is returned only once. Store it securely — it replaces the previous secret immediately and all future deliveries will be signed with the new secret.
:::
Error Responses
| Code | Status | Meaning |
|---|---|---|
invalid_token | 401 | Token is expired or invalid |
If the subscription_id does not exist or does not belong to your app:
{
"message": "Webhook subscription not found",
"status": 404
}Webhook Delivery Details
When an event fires, the platform delivers a POST request to each active subscription's target_url.
Delivery Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-SeloraX-Signature | sha256={HMAC-SHA256 signature} |
X-SeloraX-Timestamp | Unix timestamp used in the signature |
X-SeloraX-Webhook-Event | Event topic (e.g. order.created) |
X-SeloraX-Webhook-Event-Id | Unique event ID (UUID) |
X-SeloraX-Idempotency-Key | Same as event ID, for idempotent processing |
X-SeloraX-Delivery-Id | Unique delivery ID (differs per retry attempt) |
User-Agent | SeloraX-Webhook/1.0 |
Payload Structure
{
"event_id": "evt_a1b2c3d4",
"event_topic": "order.status_changed",
"store_id": 22,
"timestamp": "2026-03-02T10:30:00.000Z",
"data": {
"order_id": 1045,
"order_number": "1045",
"status": "confirmed",
"customer_name": "Rahim Ahmed",
"customer_phone": "+8801712345678",
"total": 1460.00,
"tracking_id": null,
"store_name": "My Store"
}
}The data fields vary by event topic. For order.status_changed, the platform automatically enriches the payload with customer and store information.
Signature Verification
Verify every delivery using X-SeloraX-Signature, X-SeloraX-Timestamp, and your signing_secret:
Node.js:
const crypto = require("crypto");
function verifyWebhookSignature(rawBody, signatureHeader, timestampHeader, secret) {
const signedPayload = `${timestampHeader}.${rawBody}`;
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
);
}
// Express middleware
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-selorax-signature"];
const timestamp = req.headers["x-selorax-timestamp"];
const rawBody = req.body.toString("utf8");
if (!verifyWebhookSignature(rawBody, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody);
console.log(`Received ${event.event_topic}:`, event.data);
res.status(200).send("OK");
});Python:
import hmac
import hashlib
def verify_signature(raw_body: bytes, signature_header: str, timestamp_header: str, secret: str) -> bool:
signed_payload = timestamp_header.encode() + b"." + raw_body
expected = "sha256=" + hmac.new(
secret.encode(), signed_payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)Retry Policy
Failed deliveries are retried with this schedule (6 total attempts): immediate, 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours. A delivery is considered failed if:
- Your endpoint returns a non-2xx status code
- The request times out after 15 seconds
- A network error occurs
Auto-Disable
If a subscription accumulates 20 consecutive failures, it is automatically disabled (is_active set to false). You can re-enable it by creating a new subscription.
The failure_count resets to 0 on any successful delivery.
Test Webhook Delivery
Use this tool to simulate a webhook delivery to your endpoint. Enter your webhook URL and signing secret, select an event topic, and generate a ready-to-use cURL command with proper HMAC-SHA256 signature.