Integration guide
Prefer to hand this to a coding agent? Agent-assisted integration carries the same loop as a skill file.
This is the integration in its smallest complete form: an analyst finalizes a spread, SpreadSpace tells your backend, your backend reads the frozen figures by id and stores them, and when the analyst reopens the spread your backend is told that too. Every step below was run against a live workspace as this page was written, so the shapes you see are the shapes you will get.
What you need: a SpreadSpace workspace with at least one loan that has
documents (a trial workspace’s seeded loans qualify), a key from Settings →
Live API holding spreads:read, webhooks:read and webhooks:write, and
somewhere your receiver can be reached over https. A tunnel
(cloudflared, ngrok) in front of your laptop is fine.
A test-mode key (ss_test_…) routes to the sandbox, where uploads return
premade sample extractions at no charge. That is the right place to exercise
the plumbing, but a finalize there freezes sample figures, not yours. Use a
live key against your own workspace for this walkthrough.
1. Register your endpoint
One request, once. Subscribe to both spread events. They are a pair.
The 201 returns signing_secret (whsec_…) exactly once. Store it
now. To change what the endpoint receives later, update it (PATCH /api/webhooks/{id} with subscribed_events) rather than registering again: a
second registration of the same URL is a second endpoint with its own secret.
2. Run the receiver
The SDK receiver verifies the signature over the raw bytes, drops a redelivery you have already processed, reads the snapshot the event names by id, and answers with the status the retry policy expects. You write the two handlers.
Build the receiver once per process, not per request. The default dedupe store lives on it. Python and C# read the same way; see Receive events with the SDK.
3. Finalize a spread
In the workspace (or inside your embed), open a saved spread on a loan whose board is scoped to a business filer (the figures come from the returns), then choose Finalize spread from the board menu. Confirm.
The board locks the moment the server answers: the menu now offers Reopen
spread, and replace, delete and a second finalize all refuse with
423 spread_locked until someone reopens it.
4. What arrives
One delivery reaches your endpoint, typically within seconds; allow up to a minute. This is the body (pretty-printed here; the signature covers the exact bytes as sent):
The body is a pointer, not the figures. Alongside it:
SpreadSpace-Event-Id equal to the body’s id,
SpreadSpace-Signature: t=…,v1=…, and
User-Agent: SpreadSpace-Webhooks/1.
Before your handler ran, the receiver read the snapshot the event names:
and handed it to you as attributes. It answers the snapshot itself, with no
{ loan_id, snapshot } wrapper on the by-id read:
(Money values are elided above; on the wire each is a string with exactly two decimals. Object key order on the wire is not the order shown here, so bind by name, never by position.)
Three things to note here. A year the returns do not cover is present as
null, never dropped, so absence renders as absence. finalized_by is null
for a key-authenticated caller: names of people are returned only to a
workspace user or an embed session carrying an external_user_id. And
finalized_by_external_user_id is the id you named on the embed session for
the analyst who finalized. It is your own user’s identifier handed back, so
your deal screen can name the analyst from your own table. The full
vocabulary (every attribute and ratio name, its label, unit and basis) is on
the attribute catalog.
5. Reopen, and hear it
Choose Reopen spread from the same menu. The lock clears, the snapshots stay, and your endpoint receives:
snapshot_id is the final snapshot that was current. It is unchanged and
stays on record. The figures are under revision until the next
spread.finalized, which arrives with a higher version and a new
snapshot_id. Mark your copy stale, and let the next finalize replace it.
6. Prove the plumbing
Two more moves, both from the deliveries list
(GET /api/webhooks/{id}/deliveries, then
POST /api/webhooks/{id}/deliveries/{deliveryId}/replay):
- Replay the finalize delivery. It arrives as a new delivery with the
same event
id, signed with the endpoint’s current secret. Your receiver answers200 {"received":true,"duplicate":true}and your row count does not change. - Rotate the secret (
POST /api/webhooks/{id}/rotate), pass both secrets to the receiver for the 24-hour window, and finalize again: the new delivery verifies against the new secret only.
That is the whole flow. Your system stores the numbers the analyst approved,
keyed by snapshot_id. When they are revised, you are told. Nobody re-keys
anything.