Foreshock

Get started

The shape of an integration

Three moving parts, a working order to build them in, and the one obligation that does not live on our side of the wire.

Updated
2026-08-24

A complete integration has three moving parts. Most teams build them in the order below, because each one is testable without the next.

Three moving parts

1 · Your address book. The set of addresses you want watched — deposit addresses, collection addresses, vault addresses. Registered once, then referenced by opaque customer_address_ref values in every answer. The book is encrypted per customer; matching happens over an index, so it is never decrypted to answer a query, and cross-customer reads are not expressible.

2 · The change feed. GET /v1/exposure with a cursor, plus three webhooks — exposure.match, exposure.revoked, coverage.health_changed. The webhooks are a latency optimisation over the feed. The feed is authoritative; a missed webhook is never an all-clear.

3 · The credit gate. POST /v1/decide/deposit, called once per inbound deposit before you credit it. This is the only endpoint that produces a binding verdict, and the only one whose failure modes are your problem as much as ours.

A working order

  1. Get a key. Confirm GET /v1/coverage returns a 200 and read what is in scope for your account.
  2. Register the address book. Confirm GET /v1/exposure returns a 200 and a next_cursor, even when matches is empty.
  3. Wire the cursor loop and persist the cursor. Run it before you wire webhooks — the loop is what you fall back to when webhooks fail.
  4. Wire webhook receipt, signature verification, and deduplication on event_id.
  5. Wire the credit gate behind a feature flag, in shadow mode: call it, record the verdict, do not act on it yet.
  6. Build and test the degradation branches. Every one of them must end at HOLD.
  7. Turn the flag on.

Step 6 is not a hardening pass to schedule later. It is the step that decides whether the integration is worth having.

Where the obligation sits

Everything degraded that happens inside a decision comes back as HTTP 200 with verdict: HOLD and a reason code — vendor timeout, exhausted budget, stale list, stale registry entry, coverage blind spot, decision timeout. You do not have to detect those; they are already a verdict.

The case you have to handle yourself is the one where no decision exists at all: a 5xx, a connection error, a TLS failure, a DNS failure, or a timeout on your side. There is no verdict to read, and the correct behaviour is HOLD. The official SDKs implement this by default and it cannot be switched off by configuration. If you write your own client, it is yours to implement.

Managed or self-hosted

Self-hosting is a first-class delivery form, not a fallback: the customer address book never leaves the customer environment. The contract is the same either way — the same paths, the same schemas, the same fail-closed semantics — and only the base URL differs.