Webhooks
Subscribe to real-time event notifications for sync completions, feed updates, product changes, and more.
Webhooks let you receive real-time HTTP notifications when events happen in your feed pipeline — eliminating the need to poll for status updates.
Events
| Event | Description |
|---|---|
sync.started | A feed sync has started running. |
sync.progress | A feed sync reached a progress milestone (25%, 50%, or 75%). Payload includes progress integer. |
sync.completed | A feed sync completed successfully. |
sync.failed | A feed sync failed. |
feed.created | A feed was generated for the first time for this shop. Only fires once per shop. |
feed.updated | The feed file has been regenerated. |
product.updated | A product’s override fields were updated via API. |
products.imported | Source products were persisted to the database after a sync. |
rules.changed | A feed transformation rule was created, updated, or deleted. |
api_key.created | A new API key was created. |
api_key.revoked | An API key was revoked. |
Signature verification
Every webhook delivery includes an HMAC-SHA256 signature for verification. The signing secret is returned once when you create the webhook.
X-SPF-Signature: sha256=<hex-digest>
X-SPF-Timestamp: <unix-timestamp>
To verify:
const crypto = require('crypto');
function verifyWebhook(secret, timestamp, body, signature) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
Delivery & retries
- Deliveries timeout after 10 seconds.
- Failed deliveries are retried up to 5 times with exponential backoff: 1m, 5m, 30m, 2h, 8h.
- After 5 consecutive failures across all deliveries, the webhook is automatically disabled.
- Disabled webhooks can be re-enabled via
PATCH /v1/webhooks/:idwith{"status": "active"}. Re-enabling resets the failure count to zero and clears the disabled reason.
List webhooks
/v1/webhooks Returns all webhooks for the authenticated shop.
Required scope: read_webhooks
Request
curl https://app.simpleproductfeeds.com/v1/webhooks \
-H "Authorization: Bearer spf_live_sk_your_key_here"
Response
{
"data": [
{
"id": "wh_42",
"object": "webhook",
"endpoint_url": "https://example.com/webhook",
"events": ["sync.completed", "feed.updated"],
"status": "active",
"failure_count": 0,
"last_delivery_at": "2026-03-10T12:00:00Z",
"disabled_at": null,
"disabled_reason": null,
"created_at": "2026-03-09T10:00:00Z",
"updated_at": "2026-03-09T10:00:00Z"
}
]
}
Create a webhook
/v1/webhooks Creates a new webhook subscription. The signing secret is returned once in the response — store it securely.
Required scope: write_webhooks
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
endpoint_url | string | Yes | HTTPS URL to receive webhook deliveries. |
events | string[] | Yes | Array of event names to subscribe to. |
Request
curl -X POST https://app.simpleproductfeeds.com/v1/webhooks \
-H "Authorization: Bearer spf_live_sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"endpoint_url": "https://example.com/webhook", "events": ["sync.completed", "feed.updated"]}'
Response
{
"data": {
"id": "wh_42",
"object": "webhook",
"endpoint_url": "https://example.com/webhook",
"events": ["sync.completed", "feed.updated"],
"status": "active",
"secret": "a1b2c3d4e5f6...",
"failure_count": 0,
"created_at": "2026-03-09T10:00:00Z",
"updated_at": "2026-03-09T10:00:00Z"
}
}
The secret field is only included in the create response. Store it immediately for signature verification.
Errors
| Status | Code | Description |
|---|---|---|
| 422 | webhook_invalid | Invalid endpoint URL (must be HTTPS) or invalid events. |
Get a webhook
/v1/webhooks/:id Required scope: read_webhooks
Update a webhook
/v1/webhooks/:id Update the endpoint URL, events, or status of a webhook.
Required scope: write_webhooks
Parameters
| Parameter | Type | Description |
|---|---|---|
endpoint_url | string | New HTTPS endpoint URL. |
events | string[] | Updated event subscriptions. |
status | string | Set to active to re-enable a disabled webhook. |
Delete a webhook
/v1/webhooks/:id Permanently deletes a webhook and all its delivery history.
Required scope: write_webhooks
Test a webhook
/v1/webhooks/:id/test Sends a test delivery to verify your endpoint is reachable. Returns 202 Accepted.
Required scope: write_webhooks
Rotate webhook secret
/v1/webhooks/:id/secret/rotate Generates a new signing secret for the webhook. The new secret is returned once in the response — store it securely. All subsequent deliveries will be signed with the new secret.
Required scope: write_webhooks
Request
curl -X POST https://app.simpleproductfeeds.com/v1/webhooks/wh_42/secret/rotate \
-H "Authorization: Bearer spf_live_sk_your_key_here"
Response
Returns the webhook object with the new secret field included:
{
"data": {
"id": "wh_42",
"object": "webhook",
"endpoint_url": "https://example.com/webhook",
"events": ["sync.completed", "feed.updated"],
"status": "active",
"secret": "new_secret_value_here...",
"failure_count": 0,
"created_at": "2026-03-09T10:00:00Z",
"updated_at": "2026-03-12T14:00:00Z"
}
}
List deliveries
/v1/webhooks/:id/deliveries Returns a paginated list of delivery attempts for a webhook, newest first.
Required scope: read_webhooks
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number. |
per_page | integer | 25 | Results per page (max 100). |
Delivery response fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier with whd_ prefix. |
object | string | Always "webhook_delivery". |
event | string | Event that triggered this delivery. |
status | string | Delivery status: "pending", "delivered", or "failed". |
attempt_count | integer | Number of delivery attempts. |
response_status | integer | null | HTTP response status code from your endpoint. |
response_time_ms | integer | null | Response time in milliseconds. |
error_message | string | null | Error message if delivery failed. |
delivered_at | string | null | ISO 8601 timestamp when successfully delivered. |
next_retry_at | string | null | ISO 8601 timestamp of next retry attempt. |
created_at | string | ISO 8601 timestamp when the delivery was created. |