Dispatch
1.x
Docs/Dispatch/Relay Hook
Open

Reading2 min
Updated31 Jul 2026
Sourcev1/guides/webhook-verification.mdx

The relay_hook package bridges Dispatch lifecycle events to Relay for webhook delivery. When registered as a Dispatch extension, it emits a typed webhook event at every job, workflow, cron, and shutdown lifecycle point.

Setup01

import (
    "github.com/xraph/relay"
    "github.com/xraph/dispatch/relay_hook"
    "github.com/xraph/relay/store/memory"
)

// Create a Relay instance.
r, _ := relay.New(relay.WithStore(memory.New()))

// Register the event types Dispatch will emit.
relay_hook.RegisterAll(ctx, r)

// Create the hook extension.
hook := relay_hook.New(r)

// Register with the engine.
eng := engine.Build(d,
    engine.WithExtension(hook),
)

Emitted event types02

Event typeLifecycle point
dispatch.job.enqueuedJob accepted into queue
dispatch.job.startedWorker begins execution
dispatch.job.completedJob finishes successfully
dispatch.job.failedJob fails terminally
dispatch.job.retryingJob fails, scheduled for retry
dispatch.job.dlqJob moved to DLQ
dispatch.workflow.startedWorkflow run begins
dispatch.workflow.step.completedWorkflow step completes
dispatch.workflow.step.failedWorkflow step fails
dispatch.workflow.completedWorkflow run completes
dispatch.workflow.failedWorkflow run fails
dispatch.cron.firedCron entry fires
dispatch.shutdownDispatcher shuts down

Filtering events03

Restrict which events are emitted:

hook := relay_hook.New(r,
    relay_hook.WithEvents(
        relay_hook.EventJobCompleted,
        relay_hook.EventJobFailed,
        relay_hook.EventJobDLQ,
        relay_hook.EventWorkflowFailed,
    ),
)

Event payload04

Each webhook event carries a JSON payload with the full entity at the time of the lifecycle event. For example, dispatch.job.completed includes:

{
  "job_id": "job_01h2xcejqtf2nbrexx3vqjhp41",
  "name": "send_email",
  "queue": "default",
  "elapsed_ms": 142,
  "completed_at": "2025-01-15T09:00:00Z"
}

Tenant routing05

Webhook events are routed to Relay endpoints matching the job's ScopeOrgID as the TenantID. If ScopeOrgID is empty, events are routed to a global tenant.