Skip to content

Integration Patterns

Version 1.0Last reviewed

This chapter describes technical integration only. It makes no statement about what any integration means for a regulatory obligation; see Scope.

Direction Example Constrained by
Inbound An external system pushes a record into a canister 2 MiB ingress payload, ~2 s per update call
Outbound Canister calls an external HTTP API HTTPS outcalls and the determinism constraint
Engine to engine One engine calls another Not yet available on Cloud Engines

An external system calls a canister the same way any client does: it signs its request with a key pair, and the canister identifies it by the resulting principal. See Interfaces.

Design points that follow directly from the platform, and that determine the shape of any integration:

  • Batch by aggregating, not by enlarging. The 2 MiB ingress limit is per message, and an update call costs ~2 s regardless of payload size. Many small calls are slow; one oversized call is rejected. Aggregate into 2 MiB-bounded batches.
  • Design for idempotency. A caller that times out cannot know whether its update applied. Carry a client-generated request id and have the canister deduplicate on it.
  • Verify the response signature. Client libraries do this; a hand-rolled HTTP integration typically does not, and silently forfeits the guarantee described in Trust and Security Model.
  • Authorisation lives in the canister. There is no platform-level role model to configure. Whatever a calling system is permitted to do is a function the application enforces.

A canister can call an external HTTP API through HTTPS outcalls. This is the mechanism that makes integration with conventional infrastructure possible at all, and it has a property with no equivalent in ordinary software: the request is amplified by the number of nodes in the engine, each node issues it independently, and the responses they receive must be identical. A response that differs between nodes fails consensus and is rejected.

The practical consequences are severe enough to design around from the start:

  • An endpoint returning a timestamp, a request id or any varying field needs a transform function that strips the variation before consensus. This applies to response headers as much as to the body, and headers are the usual cause of a failed outcall.
  • An endpoint that is slow, flaky or rate-limited is called by every node, not once. The endpoint has to be idempotent: the same request payload must behave the same however many times it arrives.
  • An endpoint reachable only from inside a corporate network is not reachable at all.

Transform functions and the rest of the outcall guidance are in the HTTPS outcalls documentation.

Each engine is an independent replicated state machine. Applications calling seamlessly across engines — which is what makes horizontally scaled and cross-organisational architectures work — is inherent to the protocol, but has not yet been rolled out on Cloud Engines. Work in progress at the DFINITY Foundation.

An engine does not present its state as a database that can be queried directly.

  • There is no SQL interface to an engine. Data is reached through the canister’s own methods, which the application defines. A canister can nonetheless use SQL internally: ic-rusqlite packages SQLite for canister use in Rust, compiled to wasm32-wasip1 and converted with wasi2ic. That gives the application a relational store and a query language inside the canister; it does not make the engine connectable by an external SQL client. It is a community crate from wasm-forge under an MIT licence, not part of the protocol.
  • There is no transactional guarantee spanning an engine and an external database. A write to a canister and a write to a relational system are two independent operations, and reconciling them is the integrating team’s problem.
  • For reporting, the usual pattern is to replicate data out into a conventional store rather than to query the engine analytically.