Webhooks
Webhooks enable your application to receive real-time notifications when specific events occur within the ShopWired platform. When an event happens, ShopWired sends a POST
request to the configured webhook URL.
Types of webhooks
Types of webhooks
There are two types of webhooks:
- Admin System Webhooks – Created via the admin system or using legacy credentials with the API
- OAuth Webhooks – Created by OAuth clients using the API
Each webhook is owned by its creator, meaning businesses and OAuth clients manage their own webhooks. There is a limit of 10 webhooks per topic.
Creating and managing webhooks
Creating and managing webhooks
Each webhook consists of a topic and a URL. The URL must:
- Be a valid, accessible URL
- Use the HTTPS protocol
After a webhook is created, ShopWired immediately sends a verification request to ensure the URL is valid. This verification can be resent via the API or the admin system.
Permissions
To manage webhooks through the API, OAuth clients must have the use_webhooks
permission.
Webhooks created using basic authentication will function identically to those created in the admin system.
Webhook request requirements
Webhook request requirements
- Webhook handlers must respond within 5 seconds
- A response status of
200 OK
is required for success - Failed requests will be retried for up to 24 hours
- After 24 failed attempts, the webhook will be automatically disabled
Signature verification
Signature verification
- Admin-created webhooks are signed with the secret displayed in the ShopWired account
- OAuth-created webhooks are signed with the OAuth client's signature secret
- Legacy API webhooks are signed with the admin secret (if one is configured)
The signature is sent in the X-ShopWired-Signature
HTTP header. Webhook handlers should verify the signature to confirm the request's authenticity.
Webhook payload structure
Webhook payload structure
Standard event request payload
{
"timestamp": "Tue, 23 Oct 2018 12:08:23 +0000",
"event": {
"id": 123456,
"businessId": 123456,
"createdAt": "Tue, 23 Oct 2018 12:08:23 +0000",
"topic": "product.updated",
"subjectType": "product",
"subjectId": 123456,
"data": {
"object": {
...
}
}
}
}
The data
property will usually contain an object
representing the API resource.
Special payloads
Special payloads
product.stock_changed
{
"sku": "ABC123",
"isVariation": true,
"newQuantity": 50
}
order.status_changed
{
"newStatus": "dispatched"
}
batch.completed
{
"action": "import",
"count": 100
}
Verification request payload
Verification request payload
When a webhook is first created, ShopWired sends a verification request:
{
"timestamp": "Tue, 23 Oct 2018 12:08:23 +0000",
"verificationToken": "example-token"
}
Your app must respond by signing the verificationToken
using the signature secret and returning the result as the response body.
Sample code for verification
Sample code for verification
PHP (Slim Framework)
Initial verification of webhook
public function webhook_update(Request $request, Response $response)
{
if (!$this->verifyWebhookRequest($request)) {
return $response->withStatus(404);
}
$parsed_body = $request->getParsedBody();
if (is_array($parsed_body) && !empty($parsed_body['verificationToken'])) {
$signed_verification_token = hash_hmac('sha256', $parsed_body['verificationToken'], INSERT_WEBHOOK_SECRET_HERE);
return $response->write($signed_verification_token)->withStatus(200);
}
return $response->withStatus(200);
}
Verify webhook signature
protected function verifyWebhookRequest(Request $request): bool
{
$body = $request->getBody()->getContents();
$signature = $request->getHeaderLine('X-ShopWired-Signature');
if (!$body || !$signature) {
return false;
}
$expected_signature = hash_hmac('sha256', $body, INSERT_WEBHOOKS_SECRET_HERE);
return $signature === $expected_signature;
}
Node.js (Koa Framework)
Initial verification of webhook
async webhook_update(ctx){
const verifyWebhook = await this.verifyWebhookRequest(ctx);
if (!verifyWebhook) {
return ctx.response.status = 404;
}
const body = ctx.request.body;
if (body.constructor === Object && body.verificationToken) {
const hmac = this.crypto.createHmac('sha256', INSERT_WEBHOOK_SECRET_HERE);
const data = hmac.update(body.verificationToken);
ctx.body = data.digest('hex');
return ctx.response.status = 200;
}
ctx.body = {};
return ctx.response.status = 200;
}
Verify webhook signature
async verifyWebhookRequest(ctx) {
const rawBody = ctx.request.rawBody;
const signature = ctx.request.headers['x-shopwired-signature'];
if (!rawBody || !signature) {
return false;
}
const hmac = this.crypto.createHmac('sha256', INSERT_WEBHOOKS_SECRET_HERE);
const expected_signature = hmac.update(rawBody).digest('hex');
return signature === expected_signature;
}
Available webhook topics
Available webhook topics
Topic | Trigger description |
---|---|
batch.completed | Triggered when a batch operation (e.g., import or delete) is completed |
brand.created | Triggered when a brand is created |
brand.deleted | Triggered when a brand is deleted |
brand.updated | Triggered when a brand is updated |
category.created | Triggered when a category is created |
category.deleted | Triggered when a category is deleted |
category.updated | Triggered when a category is updated |
collect_location.created | Triggered when a collection location is created |
collect_location.deleted | Triggered when a collection location is deleted |
collect_location.updated | Triggered when a collection location is updated |
customer.created | Triggered when a customer is created |
customer.deleted | Triggered when a customer is deleted |
customer.updated | Triggered when a customer's details are updated |
newsletter_subscriber.created | Triggered when a newsletter subscriber is created |
order.created | Triggered when an order is created |
order.deleted | Triggered when an order is deleted |
order.finalized | Triggered when an order is completed or paid |
order.refund.created | Triggered when a refund is created for an order |
order.refund.deleted | Triggered when a refund is deleted for an order |
order.status_changed | Triggered when an order's status is updated |
order.updated | Triggered when an order is updated |
product.created | Triggered when a product is created |
product.deleted | Triggered when a product is deleted |
product.stock_changed | Triggered when a product's stock quantity is changed |
product.updated | Triggered when a product is updated |
sale.created | Triggered when a sale is created |
sale.deleted | Triggered when a sale is deleted |
sale.updated | Triggered when a sale is updated |
shopwired_payments.charge.created | Triggered when a charge is created within ShopWired Payments |
shopwired_payments.dispute.created | Triggered when a dispute is created within ShopWired Payments |
shopwired_payments.dispute.updated | Triggered when a dispute is updated within ShopWired Payments |
shopwired_payments.payout.created | Triggered when a payout is created within ShopWired Payments |
shopwired_payments.payout.updated | Triggered when a payout is updated within ShopWired Payments |
stock_notification_batch.created | Triggered when one or more products come back in stock and email notifications are sent |
stock_request.created | Triggered when a visitor submits a back in stock request for a SKU |
tag.created | Triggered when a tag is created |
tag.deleted | Triggered when a tag is deleted |
tag.updated | Triggered when a tag is updated |
wishlist.cleared | Triggered when a wishlist is cleared |
wishlist.created | Triggered when a wishlist is created |
wishlist.deleted | Triggered when a wishlist is deleted |
wishlist.updated | Triggered when a wishlist is updated |
Retrieving events
Retrieving events
Recent events can be retrieved using the /events
API endpoint documented here. Available query parameters:
topic
since_id
created_after
created_before
count
offset
fields