cratefield-adapter-webhook-tracker
The Tracker port over a
tenant-configured webhook for the Cratefield harness (issue #432). It POSTs
the ticket as a JSON document signed with HMAC-SHA256, through the runtime's
HttpClient port — no vendor SDK, no reqwest — so the same adapter runs
unchanged on Cloudflare Workers and natively (ADR 0002: the port is core's,
the vendor client is this crate's).
Usage
use Arc;
use WebhookTracker;
use ;
use ;
let tracker = new;
let filed = tracker
.file
.await?;
The Clock is a constructor argument rather than a builder default so a
deployment that forgets it fails to compile — it is what stamps both the
payload and the signature, so the two cannot disagree.
The signature
Every delivery carries one header, Stripe-style:
Cratefield-Signature: t=<unix seconds>,v1=<lowercase hex>
The MAC is HMAC-SHA256 under the shared secret, computed over the bytes
{t}.{raw_body} — the timestamp is bound into the signature, so a
captured delivery cannot be replayed with a fresh timestamp: re-stamping
invalidates the MAC. That is the reason for this scheme over a separate,
unsigned timestamp header. The body is built once, signed, and sent — the
signed bytes are the wire bytes.
Idempotency is delegated
A webhook cannot be searched the way the sibling
cratefield-adapter-github-issues searches a repository, so the receiver
dedupes: the payload carries the draft's idempotency_key, and a repeat
delivery with the same key is answered once. There is no tracker-side id to
learn back, so the returned Filed reports the idempotency key as its id
and deduplicated: false.
Error mapping
To cratefield_core::TrackerError: 401/403 → Unauthorized, 404 →
Rejected, 422 → Rejected, 429/5xx → Transient { retry_after }
(Retry-After, both the seconds and the HTTP-date form), any other 4xx →
Rejected. A destination the HttpClient port's SSRF vetting refuses —
the URL comes from tenant config, so it is never fetched directly — is
Rejected: a config error, not weather a retry fixes. No error Display
ever includes the signing secret. A draft whose destination is not
Destination::Webhook is Rejected before any network call.
MIT. Built in the open for Cratefield, a Factory Zero venture.