Source: Gil/Ashtyn asked four integration questions they could not answer from the OSS repo or either OpenAPI spec (2026-09-13). Ground truth is in code; the specs are silent or contradictory. This issue closes the doc/spec gaps. (Code lives in relayfile-cloud; specs/docs live here in relayfile.)
1. Document the outbound webhook signature scheme (currently undocumented)
Ground truth — signer relayfile-cloud/packages/relayfile/src/middleware/auth.ts:749-768, delivery queue-consumer.ts:428-458:
- Headers:
X-Relay-Signature (lowercase-hex HMAC-SHA256, no prefix), X-Relay-Timestamp (unix seconds, string), X-Relay-Event-Id.
- Signing string:
`${timestamp}.${rawBody}` — timestamp + . + the raw JSON body.
- Key: the per-subscription secret (
task.secret), auto-generated randomBase64Url(32) and returned once at creation (durable-objects/workspace.ts:3466-3492).
- Delivery is at-least-once; receivers MUST dedupe on
X-Relay-Event-Id. Any 3xx or status >= 300 counts as failure.
Add a receiver verification guide (docs + OpenAPI description) with a code snippet: read the three headers, take the raw body bytes as received (do not re-serialize), compute HMAC-SHA256 over timestamp + "." + rawBody, lowercase-hex, constant-time compare, bound the timestamp to reject replays.
⚠️ Do not conflate with the internal ingress HMAC (signInternalHmac, auth.ts:722-747), which signs `${timestamp}\n` + body under INTERNAL_HMAC_SECRET. Both use X-Relay-Signature; the specs must clearly separate inbound-internal from outbound-partner.
2. Document the outbound delivery retry/backoff policy (currently undocumented)
From queue-consumer.ts:25,210,673: 3 attempts, exponential backoff 10s → 20s, capped 600s, ±20% jitter, 5s delivery timeout, then dead-letter. Intervals are computed at retry time and not persisted (DLQ only carries the final failedAt) — either document that, or persist an attempts_json (see the reliability issue). Add this policy to the spec/docs.
3. Reconcile the webhook-subscription path mismatch
Deployed routes (routes/webhook-subscriptions.ts, routes/webhooks.ts, registered app.ts:68-69):
POST/GET/DELETE /v1/workspaces/{workspaceId}/webhooks, POST .../webhooks/ingest, GET .../webhooks/dlq, POST .../webhooks/dlq/{deliveryId}/replay.
- Control-plane spec
openapi/relayfile-control-plane-v1.openapi.yaml models these under /v1/integrations/webhook-subscriptions (different path + body shape). Pick one and fix the other.
4. Add the missing webhook-delivery DLQ endpoints to a spec
Neither spec documents GET /v1/workspaces/{workspaceId}/webhooks/dlq or POST .../webhooks/dlq/{deliveryId}/replay. (The v1 spec's replaySyncDeadLetter/getSyncDeadLetters are a different subsystem.) Add them to openapi/relayfile-v1.openapi.yaml.
Acceptance criteria
Do not auto-merge — human review required.
Source: Gil/Ashtyn asked four integration questions they could not answer from the OSS repo or either OpenAPI spec (2026-09-13). Ground truth is in code; the specs are silent or contradictory. This issue closes the doc/spec gaps. (Code lives in
relayfile-cloud; specs/docs live here inrelayfile.)1. Document the outbound webhook signature scheme (currently undocumented)
Ground truth — signer
relayfile-cloud/packages/relayfile/src/middleware/auth.ts:749-768, deliveryqueue-consumer.ts:428-458:X-Relay-Signature(lowercase-hex HMAC-SHA256, no prefix),X-Relay-Timestamp(unix seconds, string),X-Relay-Event-Id.`${timestamp}.${rawBody}`— timestamp +.+ the raw JSON body.task.secret), auto-generatedrandomBase64Url(32)and returned once at creation (durable-objects/workspace.ts:3466-3492).X-Relay-Event-Id. Any 3xx or status >= 300 counts as failure.Add a receiver verification guide (docs + OpenAPI description) with a code snippet: read the three headers, take the raw body bytes as received (do not re-serialize), compute HMAC-SHA256 over
timestamp + "." + rawBody, lowercase-hex, constant-time compare, bound the timestamp to reject replays.signInternalHmac,auth.ts:722-747), which signs`${timestamp}\n` + bodyunderINTERNAL_HMAC_SECRET. Both useX-Relay-Signature; the specs must clearly separate inbound-internal from outbound-partner.2. Document the outbound delivery retry/backoff policy (currently undocumented)
From
queue-consumer.ts:25,210,673: 3 attempts, exponential backoff 10s → 20s, capped 600s, ±20% jitter, 5s delivery timeout, then dead-letter. Intervals are computed at retry time and not persisted (DLQ only carries the finalfailedAt) — either document that, or persist anattempts_json(see the reliability issue). Add this policy to the spec/docs.3. Reconcile the webhook-subscription path mismatch
Deployed routes (
routes/webhook-subscriptions.ts,routes/webhooks.ts, registeredapp.ts:68-69):POST/GET/DELETE /v1/workspaces/{workspaceId}/webhooks,POST .../webhooks/ingest,GET .../webhooks/dlq,POST .../webhooks/dlq/{deliveryId}/replay.openapi/relayfile-control-plane-v1.openapi.yamlmodels these under/v1/integrations/webhook-subscriptions(different path + body shape). Pick one and fix the other.4. Add the missing webhook-delivery DLQ endpoints to a spec
Neither spec documents
GET /v1/workspaces/{workspaceId}/webhooks/dlqorPOST .../webhooks/dlq/{deliveryId}/replay. (The v1 spec'sreplaySyncDeadLetter/getSyncDeadLettersare a different subsystem.) Add them toopenapi/relayfile-v1.openapi.yaml.Acceptance criteria
X-Relay-Signatureuses clearly distinguished.Do not auto-merge — human review required.