Webhooks
Webhooks let Fulify tell your system when a product changes, so you don't have to poll. Fulify sends a signed POST with a JSON body to a URL you choose each time a product is created, updated or deleted.
Add an endpoint
Webhook endpoints are set up in Settings → Webhooks, not through the API. Only organization admins can manage them.
- Go to Settings → Webhooks and click Add endpoint.
- Enter the Endpoint URL. It must be a public
httporhttpsaddress; usehttpsin production. - Optionally add a Description, such as "ERP sync".
- Tick the events to send, or leave them all unticked to receive every event.
- Click Add endpoint.
Each endpoint has its own signing secret, which starts with whsec_. Reveal or copy it from the endpoint's row on the Webhooks page. Use Test to send a signed test event to your endpoint, Disable or Enable to pause and resume it, and the bin icon to delete it.
Events
| Event | Sent when | Data |
|---|---|---|
| product.created | A product is created, in the app or through POST /products. | The full product. |
| product.updated | A product is saved in the product editor, or updated through POST /products. | The full product. |
| product.deleted | A product is deleted in the app. | The product's id and SKU only. |
Every product created or updated through POST /products fires an event, including updates that don't change any value.
The request
| Header | Meaning |
|---|---|
| X-Fulify-Event | The event name. |
| X-Fulify-Delivery | A unique id for this delivery. The same id is used on retries, so de-duplicate on it. |
| X-Fulify-Timestamp | When this attempt was signed, in Unix seconds. |
| X-Fulify-Signature | sha256= followed by the hex HMAC-SHA256 signature. |
The body:
The example product is shortened. data.product has the same fields as a product from GET /products, including categoryIds, but without variants or destinations. If you need those, call GET /products/{sku} when the event arrives.
For product.deleted, data carries only the id and SKU:
id in the body matches the X-Fulify-Delivery header. createdAt is when the event happened, which can be earlier than the attempt if the delivery was retried.
Verify the signature
Check every request before you trust it:
- Read the raw request body as bytes, before any JSON parsing.
- Compute HMAC-SHA256 over the string
{timestamp}.{raw body}, using the endpoint's secret as the key.{timestamp}is theX-Fulify-Timestampheader. - Compare the hex result with the value after
sha256=inX-Fulify-Signature, using a constant-time comparison. - Reject the request if the timestamp is more than a few minutes old, to protect against replays.
Here's an example in Node.js with Express:
Delivery and retries
- At least once. Fulify may deliver an event more than once. De-duplicate on
X-Fulify-Delivery. - Respond within 10 seconds with a 2xx status. Anything else counts as a failure: a non-2xx status, a timeout, or a redirect. Redirects aren't followed, so give Fulify the final URL.
- Retries. A failed delivery is retried after 1 minute, 5 minutes, 15 minutes, 1 hour and 6 hours, for 6 attempts in total. After the last attempt it's dropped.
- Order isn't guaranteed. Retries mean events can arrive out of order. Use the product's
updatedAtto decide which version is newest. - Automatic disabling. After 15 failed deliveries in a row, the endpoint is disabled and the Webhooks page shows "Auto-disabled after repeated failures". Pending deliveries for it are dropped. Fix your receiver, then click Enable to resume. Events that happened while it was disabled aren't re-sent, so backfill with an
updatedSincepoll.
Test your endpoint
Click Test next to an endpoint to send a signed ping event straight away. The page shows whether your endpoint answered with a 2xx status.
The test is signed the same way as real deliveries, so it checks your signature verification too. Its X-Fulify-Delivery header is test. Test deliveries aren't retried.