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

EventDescription
sync.startedA feed sync has started running.
sync.progressA feed sync reached a progress milestone (25%, 50%, or 75%). Payload includes progress integer.
sync.completedA feed sync completed successfully.
sync.failedA feed sync failed.
feed.createdA feed was generated for the first time for this shop. Only fires once per shop.
feed.updatedThe feed file has been regenerated.
product.updatedA product’s override fields were updated via API.
products.importedSource products were persisted to the database after a sync.
rules.changedA feed transformation rule was created, updated, or deleted.
api_key.createdA new API key was created.
api_key.revokedAn 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/:id with {"status": "active"}. Re-enabling resets the failure count to zero and clears the disabled reason.

List webhooks

GET /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

POST /v1/webhooks

Creates a new webhook subscription. The signing secret is returned once in the response — store it securely.

Required scope: write_webhooks

Parameters

ParameterTypeRequiredDescription
endpoint_urlstringYesHTTPS URL to receive webhook deliveries.
eventsstring[]YesArray 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

StatusCodeDescription
422webhook_invalidInvalid endpoint URL (must be HTTPS) or invalid events.

Get a webhook

GET /v1/webhooks/:id

Required scope: read_webhooks


Update a webhook

PATCH /v1/webhooks/:id

Update the endpoint URL, events, or status of a webhook.

Required scope: write_webhooks

Parameters

ParameterTypeDescription
endpoint_urlstringNew HTTPS endpoint URL.
eventsstring[]Updated event subscriptions.
statusstringSet to active to re-enable a disabled webhook.

Delete a webhook

DELETE /v1/webhooks/:id

Permanently deletes a webhook and all its delivery history.

Required scope: write_webhooks


Test a webhook

POST /v1/webhooks/:id/test

Sends a test delivery to verify your endpoint is reachable. Returns 202 Accepted.

Required scope: write_webhooks


Rotate webhook secret

POST /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

GET /v1/webhooks/:id/deliveries

Returns a paginated list of delivery attempts for a webhook, newest first.

Required scope: read_webhooks

Parameters

ParameterTypeDefaultDescription
pageinteger1Page number.
per_pageinteger25Results per page (max 100).

Delivery response fields

FieldTypeDescription
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.