Webhooks
Receive signed events when requests and products change.
Webhooks push events to an HTTPS endpoint you control, so you do not have to poll. Create endpoints in your vendor workspace under Settings, then Webhooks, or through the API with the webhooks:write scope. Each endpoint chooses the events it wants and has its own signing secret.
The secret starts with whsec_ and is shown once, when you create or rotate the endpoint.
Events
| Event | Sent when |
|---|---|
request.created | A buyer sends a new request to your brands. |
request.updated | A request changes status or gets a new message. |
product.published | One of your products goes live. |
product.unpublished | A published product is pulled, sent back to review or deleted. |
A vendor endpoint receives only the events of its own vendor.
Delivery
ArchFindr sends a POST with a JSON body:
{
"event": "request.created",
"createdAt": "2026-03-04T09:21:07.512Z",
"data": {
"id": "0198a1f2-7c3e-7d4b-9a10-5b6c8e2f1a34",
"reference": "RQ-10482",
"status": "new",
"type": "price",
"subject": "Price for the Oslo lounge chair",
"providerId": "0198a1f0-1b2c-7a3d-8e4f-6a7b8c9d0e1f",
"brandId": "0198a1f0-4d5e-7f60-9a1b-2c3d4e5f6071",
"productId": "0198a1f1-8a9b-7c0d-9e1f-3a4b5c6d7e8f",
"customer": { "name": "Lina Haddad", "email": "[email protected]", "company": "Haddad Studio", "country": "SA" },
"messagesCount": 1
}
}The data object depends on the event. Request events carry the request. Product events carry id, slug, name, status, brandId and providerId.
These headers come with every delivery:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | ArchFindr-Webhooks/1 |
X-ArchFindr-Event | The event name, such as request.created. |
X-ArchFindr-Delivery | The delivery id. Use it to deduplicate. |
X-ArchFindr-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
Respond with any 2xx status within 10 seconds. Redirects are not followed.
Verify the signature
The signature is the hex HMAC-SHA256 of <t>.<raw body> computed with your endpoint secret. Verify the raw bytes before parsing the JSON, compare in constant time, and reject timestamps older than five minutes to stop replays.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifyWebhook(secret: string, rawBody: string, header: string, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=') as [string, string]))
const timestamp = Number(parts.t)
if (!timestamp || !parts.v1) return false
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const given = Buffer.from(parts.v1)
const wanted = Buffer.from(expected)
return given.length === wanted.length && timingSafeEqual(given, wanted)
}Use the raw body
Re-serialising parsed JSON changes the bytes and breaks the signature. In Express, use express.raw({ type: 'application/json' }) on the webhook route.
Retries
A delivery that fails (a non-2xx answer, a timeout or a network error) is retried up to five more times, six attempts in total, with exponential backoff that starts at 10 seconds and caps at 30 minutes. After the last attempt the delivery is marked failed. You can inspect deliveries, retry one, or send a test event from the endpoint page in Settings.
Deliveries can arrive more than once and out of order, so make your handler idempotent: deduplicate on X-ArchFindr-Delivery.
Endpoint requirements
In production the URL must be a public https address. Private network addresses are refused.