Listening locally
Watch your brand's events as they happen, forward them to your own endpoint signed the way your webhooks are, and fire any event yourself with trigger.
While dev runs, every job writes events to your brand's feed, in order. listen prints them as they land, --forward-to delivers them to your endpoint as signed webhooks, and trigger sends one in. Your events covers who receives which.
--forward-to makes a watch for your URL. dev delivers each event there as a real webhook: signed with the watch's whsec_ secret in services-signature, and retried when your endpoint fails. Stopping listen deletes the watch. --events narrows it to the events you name.
Here a Filing waits on the client's signature and on the registry's receipt, and the endpoint gets both:
magic values: client.email = signs@example.test
$ npx services.dev listen --forward-to http://localhost:4242/hook --events job.ordered,ask.opened,receipt.accepted,job.delivered
Ready! Forwarding to http://localhost:4242/hook as wh_1rKV5VS8V3. Its signing secret is whsec_88PiGzGqWulKhuHAUGldLH9GSl5J8tTF (^C to quit)
# in another terminal
$ npx services.dev filing "client=Ada Lovelace <signs@example.test>" document=lease.pdf --max 49
job_7Hq2 · working · $49.00 held
job_7Hq2 · delivered · lease.filing.pdf · $49.00 charged
record prepare · ai | sign · client · sign | review · ai · pass | file · the registry · accepted · rcp_7Hq2
ledger +$49.00 · AI $0.84 · you $48.16
next npx services.dev accept job_7Hq2
# listen
2026-10-25 14:02:11 --> job.ordered [evt_5gX1X2B5swzY61]
2026-10-25 14:02:11 <-- [200] POST http://localhost:4242/hook [evt_5gX1X2B5swzY61]
2026-10-25 14:02:11 --> ask.opened [evt_RpMfNTkcGvEOR4]
2026-10-25 14:02:11 <-- [200] POST http://localhost:4242/hook [evt_RpMfNTkcGvEOR4]
2026-10-25 14:02:11 --> receipt.accepted [evt_0NJwixDHOuv1kX]
2026-10-25 14:02:11 <-- [200] POST http://localhost:4242/hook [evt_0NJwixDHOuv1kX]Your endpoint checks each delivery with webhooks.verify() from services.dev/agent, and gets the typed event back. To see a retry, forward to a magic host such as https://fails-once.example.test/hook: its first delivery of each event fails. Magic values lists the hosts.
Without --forward-to, listen prints what a buyer's key receives. Run it in one terminal, and work in another:
magic values: none
$ npx services.dev listen
Ready! Listening at http://localhost:8787/events/stream (^C to quit)
# in another terminal
$ npx services.dev trigger job.delivered
evt_5gX1X2B5swzY61 · seq 1 · job.delivered · synthetic
$ npx services.dev translation document=https://example.com/lease.pdf to=fr-CA --max 89
job_7Hq2 · working · $89.00 held
job_7Hq2 · waiting · on signature · $89.00 held
record translate · ai | harden · ai · adversarial · 2 lenses · pass
next npx services.dev approve job_7Hq2
$ npx services.dev trigger revised job=job_7Hq2
evt_w2jh2w27S71PvA · seq 13 · signal.raised · revised · goto translate
$ npx services.dev trigger revisde job=job_7Hq2
revisde isn't an inbound signal of Translation: it takes revised.trigger <type> is events send. Name an event of the catalog and it fires as a synthetic event, to its audience like any other, so you can test your endpoint for an event no job has reached yet. Name a job too, and it raises that job's inbound signal. This Translation takes one, revised, for when the buyer's source document changes:
import { Service } from 'services.dev'
export default Service({
name: 'Translation',
description: 'A document, translated and signed.',
input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
output: { document: 'file' },
signals: { revised: { inbound: true, goto: 'translate' } },
deliver: {
translate: 'ai',
harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
},
human: { sign: 'sworn-translator', stamp: true },
price: { usd: 89, payout: 30 },
})Above, revised moved the waiting job back to translate, and the record shows the redo. A name the Service doesn't take is refused with exit code 3, invalid_request, and the job is untouched.
Signals covers how a Service declares them, and Events lists the catalog.