AsyncAPI 3.1 for your Sume webhook receiver: a 29-line spec
Describe the receiver Sume calls in AsyncAPI 3.1.0: one receive operation, two signature headers, three job events. Parsed with the AsyncAPI parser.

Write an AsyncAPI 3.1.0 document with one channel for your webhook path, a receive operation, and a message whose headers are x-sume-webhook-timestamp and x-sume-webhook-signature and whose payload has event and job_id. The AsyncAPI reference (read 2026-10-10) lists 3.1.0 as the current version and defines action as send or receive, which is how a receiver is described in version 3.
The document below parses cleanly with the @asyncapi/parser package and reports version 3.1.0. It documents your receiver. It is not a Sume-published spec; Sume documents webhooks in prose and in its OpenAPI file.
The document
The three event names come from the Webhooks docs: job.completed, job.failed and job.canceled. Sume sends terminal job events only, with no progress or partial deliveries, so the payload enum has exactly those three values. status is OK for a completed job and ERROR for failed and canceled ones, which carry an error object that this short sketch leaves out.
asyncapi: 3.1.0
info: {title: Sume job webhooks, version: 1.0.0}
channels:
sumeJobs:
address: /sume/webhook
messages:
jobTerminal: {$ref: '#/components/messages/jobTerminal'}
operations:
receiveJobEvent:
action: receive
channel: {$ref: '#/channels/sumeJobs'}
messages:
- $ref: '#/channels/sumeJobs/messages/jobTerminal'
components:
messages:
jobTerminal:
headers:
type: object
required: [x-sume-webhook-timestamp, x-sume-webhook-signature]
properties:
x-sume-webhook-timestamp: {type: string}
x-sume-webhook-signature: {type: string}
payload:
type: object
required: [event, job_id]
properties:
event: {enum: [job.completed, job.failed, job.canceled]}
job_id: {type: string}
status: {enum: [OK, ERROR]}What the document gives you
| AsyncAPI field | Value in the sketch | Sume fact |
|---|---|---|
| operations.receiveJobEvent.action | receive | Your server receives the POST |
| channels.sumeJobs.address | /sume/webhook | Your path, over public HTTPS |
| message headers | timestamp and signature | Signed as HMAC SHA-256 over timestamp, a dot, and the raw body |
| payload.event | three terminal events | No progress events exist |
What it cannot express
AsyncAPI describes shapes, not the signature algorithm, the 5-minute replay window, the 10-attempt retry schedule or the job_id idempotency rule. Put those in the description of the operation, and test them in code. A schema check that passes on a forged body is not security, which is why the verifier stays in your handler and refuses an empty secret.
During a secret rotation the signature header can carry several comma-separated sume-v1= entries, so type the header as a string, not as a single hex value.
Use it for contract tests
Generate a mock sender from the document, post sample payloads to your handler, and assert it answers 2xx for a valid signature and 401 for a bad one. Redelivery after your handler stored the event must not create a second record, so add a case that sends the same job_id twice. The document stays useful as the single page a new teammate reads before touching the receiver.
Sources
Related posts
More in Developers
- Idempotency keys for Sume batches: item index plus payload hash
A deterministic Idempotency-Key makes a rerun return the original jobs, and a changed payload gets a new key, avoiding 409 idempotency_conflict.
- Bun 1.4.3 ERR_PROXY_TUNNEL: is it a Sume error or your proxy?
Bun 1.4.3 rejects fetch with ERR_PROXY_TUNNEL on a failed CONNECT. A Sume error always carries error.request_id; use it to tell the two apart.
- Bun 1.4.3 fake timers: test a Sume poll loop with request timeouts
Bun 1.4.3 fixes advanceTimersByTime spinning with AbortSignal.timeout. Test a Sume job poll loop that honors next_poll_after_seconds without real waits.
- Bun 1.4.3 fetch Content-Length check: send a Sume JSON body as bytes
Bun 1.4.3 rejects fetch when a declared Content-Length mismatches a stream body. For a Sume submit, skip the header and pass the encoded JSON.
Written by Sume