furniops listen
Receive real FurniOps events on your laptop, signed exactly as production deliveries are, without a tunnel.
FurniOps delivers webhooks only to public HTTPS addresses, so a handler on localhost cannot
receive them directly. furniops listen turns that around: it holds the
event stream open from your machine and POSTs each event
to your local handler, signed with the Standard Webhooks v1 scheme exactly as FurniOps signs a
real delivery. A handler that verifies these will verify production deliveries unchanged.
furniops listen --forward-to localhost:3000/webhooksReady. Forwarding events to http://localhost:3000/webhooks. Your webhook signing secret is whsec_… (^C to quit)
10:00:02 offer.changed evt_034XqBaL2mRLTZPXyRbaUi -> POST http://localhost:3000/webhooks [204] 12 ms
10:00:09 product.updated evt_034XqBaL2mRLTZPXyRbaUj -> POST http://localhost:3000/webhooks [500] 3 msThe local secret
listen signs with a secret of its own, made the first time and kept in the profile, so it stays
the same across runs and you set it once. It is not any endpoint's secret: it never leaves your
machine. Print it on its own to configure your handler:
export FURNIOPS_WEBHOOK_SECRET=$(furniops listen --print-secret)Production endpoints have their own secrets, returned when you create them; switch the variable when you deploy.
A full loop
Run your handler
Any handler that verifies with the SDK, such as the one in Your first webhook:
FURNIOPS_WEBHOOK_SECRET=$(furniops listen --print-secret) node server.tsStart listening
furniops login --with-api-key # a fo_test_ key, once
furniops listen --forward-to localhost:3000/webhooks --events offer.changed,offer.createdMake something happen
furniops trigger offer.changedThe sample event goes onto your sandbox's stream; listen forwards it and prints your handler's
answer.
What each delivery carries
| Header | Value |
|---|---|
webhook-id | The event ID. |
webhook-timestamp | When listen sent it, in Unix seconds. |
webhook-signature | v1, and the HMAC-SHA256 signature with the local secret. |
FurniOps-Version | The API version the stream answered in. |
Content-Type | application/json |
The body is the event envelope exactly as the stream delivered it.
How it differs from production
listen is for development. It forwards what the stream carries, and delivers each event once:
| Production delivery | furniops listen | |
|---|---|---|
| Signature, headers, body | Standard Webhooks v1 | The same, with the local secret |
| A failed answer | Retried for about three days | Printed, not retried |
| Redirects | Never followed | Never followed |
| Timeout | 15 s | 30 s |
| Filters | The endpoint's types, sources and categories | --events only |
| Coalescing | The endpoint's coalesce_seconds | None |
When listen is not running | n/a | Events are not forwarded |
A handler that answers in time and survives a redelivery behaves the same in both.
Options
| Flag | Meaning |
|---|---|
-f, --forward-to <url> | Where to POST each event. localhost:3000/webhooks gets http://. |
--events <types> | Comma-separated event types to forward. Default: all. |
--payload <kind> | thin (default) or fat: fat events carry the object as of the event's version. |
--print-secret | Print the local signing secret and exit. |
--json | One JSON line for the ready message (with forward_to and secret), then one per delivery with event_id, event_type, url, status, duration_ms and any error. |
listen runs until Ctrl+C. When the stream drops, it reconnects and resumes after the last event
it forwarded.
Live or test
listen forwards the events of the mode it acts in: a test key's sandbox, a live key's live data,
or, for an account session, live data unless you pass --mode test. Sample events from
furniops trigger exist only in test mode, so develop with a test key or --mode test.