API security
Financial spreading API security compared with Ocrolus
How the SpreadSpace API secures connections, retries and webhooks, compared with Ocrolus and set against the security hygiene of Stripe and Plaid.
A financial spreading API carries a borrower’s tax returns, bank statements and financial statements between two companies’ systems. It reads documents and returns spreads. It does not move money like Stripe or pull data out of bank accounts like Plaid, and an API that does either answers to a stricter standard. The fair bar for a spreading API is another API that reads lending documents, so this guide uses Ocrolus as that bar and treats Stripe and Plaid as the security hygiene above it.
A security review can check three protocols at the level of each request. Every connection should run over TLS 1.2 or later, every call that changes data should accept an idempotency key so a retry never runs twice, and every webhook should carry an HMAC-SHA256 signature over a timestamp and the body.
The SpreadSpace API runs all three. Ocrolus’s API runs TLS and offers signed webhooks, and its documentation names no idempotency key. This guide sets the two side by side on each protocol, from Ocrolus’s published documentation and a TLS handshake test we ran against each production API host on November 3, 2026, then sets SpreadSpace beside Stripe and Plaid and reports what other spreading vendors document.
Three protocols and what each one stops
Each protocol closes a specific failure. TLS keeps a request private and unaltered on the wire. An idempotency key turns a retry after a timeout into a safe operation rather than a second upload or a second export. A webhook signature lets the lender’s system prove that an event came from the vendor and was not changed on the way.
| Protocol | What it stops | How SpreadSpace applies it |
|---|---|---|
| TLS 1.3 and TLS 1.2 | Reading or altering a request in transit, and downgrades to older protocol versions | The API host refuses TLS 1.0 and 1.1 and every cipher suite without forward secrecy |
| Idempotency keys | A retried request creating a second upload, export or record | An Idempotency-Key header on a POST, PUT, PATCH or DELETE call replays the first response for 24 hours |
| Request fingerprint | A reused key quietly applying to a different request | The method, path and body are fingerprinted with the key, and a mismatch is refused |
| Signed webhooks | A forged or altered event reaching the lender’s system | Every delivery carries an HMAC-SHA256 signature over a timestamp and the raw body |
| Replay window | A captured delivery being sent again later | The official SDKs refuse a signature whose timestamp sits more than five minutes from the receiver’s clock |
| Secret rotation | A secret change dropping deliveries | The previous webhook secret stays valid beside the new one for 24 hours |
None of the three is exotic. Stripe documents all of them, and an engineering team that has integrated payments already knows how to use them. What a lender should check is whether a spreading vendor documents them, applies them to every call that changes data, and holds its production host to the TLS versions it states.
SpreadSpace and Ocrolus on each protocol
Ocrolus is the closest comparison, because its API reads bank statements and other lending documents for lenders, as SpreadSpace does. On transport the two production hosts returned the same result. Each accepted TLS 1.3 and TLS 1.2, refused TLS 1.1 and TLS 1.0, accepted only TLS 1.2 cipher suites with forward secrecy and sent a Strict-Transport-Security header for a year. For webhooks both use HMAC-SHA256 over a timestamp and the body, and Ocrolus adds a request id to what it signs.
| Protocol | SpreadSpace | Ocrolus |
|---|---|---|
| TLS versions | TLS 1.3 and TLS 1.2 | TLS 1.3 and TLS 1.2 |
| TLS 1.2 suites without forward secrecy | Refused | Refused |
| Idempotency keys | An Idempotency-Key header on every call that changes data, honored for 24 hours | Not documented |
| Webhook signatures | On every delivery, with no setting to turn them off | Opt-in |
| Webhook secret | Generated by SpreadSpace from 192 random bits and shown once | Generated and registered by the customer, 16 to 128 characters |
| What the signature covers | A timestamp and the raw body | A timestamp, a request id and the body |
| Replay window | Five minutes, enforced by the official SDKs | Not documented |
The differences start with what a customer has to set up. Ocrolus lists its webhook signature, its IP allowlist and HTTP basic authentication as opt-in methods, so an endpoint registered without them receives deliveries it has no way to check. SpreadSpace signs every delivery, and no setting turns the signature off. Ocrolus asks each customer to generate a secret of 16 to 128 characters and register it through its API, while SpreadSpace generates each endpoint’s secret from 192 random bits and shows it once. Ocrolus documents no limit on the age of the timestamp it signs, and the official SpreadSpace SDKs refuse a delivery whose timestamp sits more than five minutes from the receiver’s clock.
The widest gap is the protocol Ocrolus does not document. Its API reference, read page by page, names no idempotency key, so when a create call times out an Ocrolus integration has to work out on its own whether the call ran before it tries again. SpreadSpace replays the first response to a retried call that carries the same key for 24 hours, and its SDKs attach the key without any code from the integration.
TLS measured on the production API hosts
TLS protects a request on the wire, and the versions and cipher suites a host accepts decide how strong that protection is. A connection to api.spreadspace.app negotiates TLS 1.3 or TLS 1.2. Earlier versions are refused, and so is every TLS 1.2 cipher suite whose key exchange lacks forward secrecy, so a recording of today’s traffic cannot be decrypted later with a stolen server key.
| Handshake offered | SpreadSpace | Ocrolus | Stripe | Plaid |
|---|---|---|---|---|
| TLS 1.3 | Accepted | Accepted | Accepted | Refused |
| TLS 1.2 | Accepted | Accepted | Accepted | Accepted |
| TLS 1.1 and TLS 1.0 | Refused | Refused | Refused | Refused |
| TLS 1.2 suites without forward secrecy | Refused | Refused | Accepted | Refused |
Anyone can repeat the test. We opened a handshake at each TLS version against api.spreadspace.app, api.ocrolus.com, api.stripe.com and production.plaid.com, then offered each host the TLS 1.2 cipher suites one at a time. SpreadSpace, Ocrolus and Stripe accepted TLS 1.3 and TLS 1.2, and Plaid’s production host accepted TLS 1.2 and refused TLS 1.3. Of the 155 TLS 1.2 suites offered, Stripe’s host accepted four with RSA key exchange, which carry no forward secrecy, while every suite that the other three hosts accepted carries it.
Every hop behind the security edge is encrypted as well, up to the service that answers the request, and the API sends a Strict-Transport-Security header that tells browsers to use HTTPS for a year. Browsers also ship with a preload list of domains they reach only over HTTPS, and every .app domain is on it, spreadspace.app included, so a browser never opens a plain HTTP connection to a SpreadSpace host, even on a first visit. Stripe and Plaid have their own domains on the same list, and ocrolus.com is not on it. Webhook deliveries travel the other way over HTTPS only, and an endpoint registered with a plain http address is refused at registration.
Stripe integration security guide
Idempotency keys and safe retries
A network error leaves a client unsure whether its request ran. Without an idempotency key, a retry can create a second export or a second upload, and the integration has to list what exists before it can safely try again. Stripe answered this with an Idempotency-Key header, and SpreadSpace accepts the same header on every POST, PUT, PATCH and DELETE call made with an API key.
| Behavior | SpreadSpace | Stripe | Plaid |
|---|---|---|---|
| Where the key goes | Idempotency-Key header | Idempotency-Key header | idempotency_key field in the request body |
| Calls that accept it | Every POST, PUT, PATCH and DELETE made with an API key | Every POST in API v1 | Transfer requests such as authorizations |
| Longest key | 255 characters | 255 characters | 50 characters |
| How long a key is honored | 24 hours | At least 24 hours | 48 hours on an authorization |
| Key reused for a different request | Refused with 409 idempotency_request_mismatch | Refused with an error | Not documented |
| Duplicate sent while the first is running | Waits up to 30 seconds and replays the first response | Not stored, and the client retries | Not documented |
| Server errors | Replayed, except a 429 or 503 that asks for a retry | Replayed, including 500 errors | Not documented |
SpreadSpace stores each response under the API key that sent it, so two credentials never share an entry, and it checks the caller’s current scopes before it replays anything, so a replay never returns what the key can no longer read. The official TypeScript, Python and C# SDKs generate a key for every call that changes data and reuse it on their own retries, so an integration has the protection without writing code for it.
Plaid documents its key as a field in the body of its transfer requests, such as a transfer authorization, where a duplicate could charge an account twice.
Signed webhooks and replay protection
A webhook is a request the lender’s system did not ask for, which makes it the easiest request to forge. SpreadSpace signs every delivery with the scheme Stripe uses. The SpreadSpace-Signature header carries a timestamp and an HMAC-SHA256 of the timestamp joined to the raw body, keyed with the endpoint’s own secret, so a changed byte or an old timestamp fails verification.
| Behavior | SpreadSpace | Stripe | Plaid |
|---|---|---|---|
| Signature | HMAC-SHA256 | HMAC-SHA256 | ES256 JSON Web Token |
| What the signature covers | A timestamp and the raw body | A timestamp and the raw body | The issue time and a SHA-256 hash of the raw body |
| Header | SpreadSpace-Signature | Stripe-Signature | Plaid-Verification |
| Default replay window | Five minutes either side of the receiver’s clock | Five minutes | Five minutes from issue |
| Rotation | The previous secret stays valid beside the new one for 24 hours | The previous secret can stay valid for up to 24 hours | Keys roll over by key id |
Plaid reaches the same guarantee by another route. Its Plaid-Verification header holds a JSON Web Token signed with ES256, a public-key signature, and the token carries a SHA-256 hash of the body. A public key spares the receiver from storing a shared secret, and a per-endpoint HMAC secret spares it a key fetch and a token library. Both tie the signature to the exact body and to a five-minute window, which is the property that matters.
Each SpreadSpace delivery also names its event in a SpreadSpace-Event-Id header that stays the same across every retry and replay, so a receiver can drop duplicates. Deliveries go only to HTTPS endpoints, never follow a redirect, and leave from one published address for receivers that allowlist by source IP. The official SDKs compare signatures in constant time and hand the handler a parsed event only after the signature passes.
Webhooks in the SpreadSpace API documentation
Keys, scopes and isolation
The protocols protect each request, and the credential behind it decides what a stolen key could reach. A SpreadSpace API key is shown once, at creation, and stored only as a salted hash. Each key carries an explicit set of scopes, and its prefix names its mode. A key that starts with ss_test_ reads an isolated sandbox and can never read live data, the same separation Stripe draws between its test and live keys.
Every record belongs to one organization. A request that names a record in another organization answers 404, the same as a record that does not exist, so an id never reveals what exists elsewhere. A rotated key stays valid for a grace period chosen at rotation, 24 hours by default, so a deploy can move to the new key without a gap. Per-client rate limits and a web application firewall stand in front of the API, and API events are logged with up to seven years of retention.
What other spreading vendors document
On November 2, 2026 we read the public API documentation of the spreading and lending document vendors that publish one. None of them documents idempotency keys, so none documents all three protocols, and a retried upload or export is left for each integration to reconcile. Five of the six sign their webhooks with HMAC-SHA256, the algorithm SpreadSpace and Stripe use, but only Ocrolus includes a timestamp in what it signs. A signature over the body alone still verifies when a captured delivery is sent again a day later.
| Vendor | Idempotency keys | Webhook authentication |
|---|---|---|
| SpreadSpace | An Idempotency-Key header on every call that changes data, honored for 24 hours | HMAC-SHA256 over a timestamp and the raw body |
| Ocrolus | Not documented | HMAC-SHA256 over a timestamp, a request id and the body |
| Heron Data | Not documented | HMAC-SHA256 over the body, without a timestamp |
| Inscribe | Not documented | HMAC-SHA256 over the body, without a timestamp |
| UPTIQ Document AI | Not documented | HMAC-SHA256 over the body, without a timestamp |
| nCino eVault and mortgage APIs | Not documented | HMAC-SHA256 over the body, without a timestamp |
| Abrigo | Not documented | The webhook’s secret key itself, sent in a header |
nCino’s spreading runs inside its banking platform, and the business banking documentation on its developer portal requires a customer login, so the table shows the eVault and mortgage APIs it documents in public. Several other spreading vendors publish no API reference at all, and for them the questions in the next section are the only way to learn the answers. A vendor may also run protections it does not document, which is one more reason to test the production host and ask.
Ocrolus webhook signature verification
What to ask a spreading API vendor
Ask for the production API host and test it yourself. A handshake at each TLS version needs only standard tools, and the result is a fact rather than a questionnaire answer. Then ask what happens when a create call times out and the client sends it again. When the answer is that the client should list what exists and decide, the vendor has no idempotency keys, and every integration has to build that reconciliation on its own.
Ask how a webhook proves where it came from. A shared key in a header proves little, because anyone who sees one delivery holds the key and can send any payload with it, and an IP allowlist only says which network sent the request. A signature over a timestamp and the body is the standard Stripe and Plaid both meet, and it is the one to require. Ask too whether every delivery carries the signature or only deliveries to an endpoint the customer has configured, and who generates the secret.