Integration Authoring Guidelines
Design an integration so customers can configure it for their accounts and continue using it as their data and workflows change. These guidelines cover customer settings, organizing flows, recovering from failures, and upgrades for both code-native integrations and integrations built with the low-code designer.
Define what customers can expect
Use the integration's name and description to explain which apps it connects, which records move, in which direction, and what starts the sync. For example, "Send expenses to Acme Accounting when they're approved" describes a different task from "Import expenses from Acme Accounting every hour."
During configuration, explain choices that affect the customer's data: whether activation imports historical records, which system wins when both change a record, and how deletions are handled. For a bidirectional sync, decide how to recognize changes made by the integration so those changes don't keep triggering updates back and forth. The integration patterns provide starting points for data syncs, notifications, and other use cases.
Design configuration around customer decisions
Keep account-specific settings in config variables so the same integration can serve customers with different accounts, destinations, and preferences. Use connections for authentication, and choose their scope according to who owns and reuses the connected account.
Present settings in terms customers recognize. For example, a data source can let them select a project by name instead of finding its API ID. If that choice requires an authenticated account, collect the connection first.
Explain what the defaults do, especially when they affect how much data is imported or which records are updated. For settings your team manages, use config variable visibility to keep them out of the customer's configuration.
Use the hosted config wizard when its pages and controls fit your configuration experience. For a code-native integration whose configuration needs your app's own UI, see headless configuration.
Test the configuration experience with a second account whose projects, permissions, and optional fields differ from the account you built against. Those differences can expose assumptions about which choices are available to customers.
Divide flows by the work they perform
Give each flow a task you can run and investigate independently, such as Import Contacts or Send Approved Expenses. Separate flows are useful when work has different triggers, schedules, or recovery needs. Keep related work together when splitting it would require coordinating several executions to complete one operation.
Choose a trigger based on how the source system exposes changes. App event triggers can receive webhooks or poll for changes; a scheduled flow suits work you want to initiate periodically.
If one flow delegates work to another, pass the required data in the cross-flow invocation. Persisted state is written when an execution completes successfully, so a child flow started before its caller finishes can't rely on reading the caller's new state.
Plan for the first sync as well as later updates
A customer activating an integration may have years of history, even if later executions process only a few changes. Choose a starting point and explain whether activation imports existing records or only watches for new ones.
For large imports, use trigger pagination and batching to fetch pages and process smaller groups of records in separate executions. Fetch incrementally: collecting every page in one array can exhaust memory even if you later split that array into batches. Choose batch size according to the work per record, and limit concurrent batches to suit the destination API's capacity. The linked guide covers the code-native pattern and links to its custom-trigger equivalent for low-code integrations.
When a small, bounded dataset fits in one execution, a paginated loop may be sufficient. Check the expected record size and number of API requests against runner limits, including the time spent waiting for requests and retries.
Make retries safe for records already processed
A failure can occur after an external API has accepted a write but before your flow receives its response. Retrying that request can create a duplicate unless the destination recognizes it as the same operation. Where supported, use a destination API's idempotency key or an upsert keyed by the source record's identity. Choose the key according to the operation: a record ID may suit an upsert, while separate events for that record may need separate event IDs.
Flow-level retry restarts the flow from its first step. As the flow runs, it can send API requests again even if the destination processed them before the previous execution failed. To retry a smaller operation, use step-level error handling in low-code or error handling around the relevant request in code-native.
For temporary failures, limit retry attempts and wait between them. Invalid configuration or missing permissions need correction before another attempt can help.
If the flow continues after failing to process a record, retain the record ID and the failed operation so you can find the record and retry that operation. In low-code, branch on the ignored error to record that failure or notify your team.
Coordinate state with concurrent executions
Use persisted state for small checkpoints such as a pagination cursor or last successful sync time. Choose the narrowest scope that needs the value: flow state for one flow, cross-flow state for flows in one instance, and integration state only when the value is intentionally shared across customer instances.
State is loaded at the start of an execution and saved when it completes successfully. Concurrent executions that change the same state can overwrite one another's updates, even if they change different keys.
For a supported webhook flow that updates a shared checkpoint, sequential execution can keep that flow's executions from overlapping. Sequential execution doesn't serialize sibling flows or make writes to an external API happen only once. Use an external data store with the coordination guarantees you need when multiple flows must update shared records.
An external write can succeed even when the execution fails and its state changes aren't saved. For example, if a flow creates 20 records and then fails, those records may already exist at the destination when the flow restarts. The flow needs to recognize those records or use API requests that won't create duplicates. Advance a checkpoint only after the work it represents has succeeded or its failures have been recorded for recovery.
Leave enough evidence to resolve failures
Log the operation, source record ID, and outcome so your team can connect an execution to a customer's report. Prefer those identifiers and counts over entire payloads, especially when records contain personal data or credentials. For code-native integrations, use logging sections around operations you need to inspect separately, such as fetching records and writing them to the destination.
When a customer can resolve an error, tell them what to do next, such as reconnecting an account or selecting a project they can access. Use alert monitors to surface failures that need intervention, and establish who handles them before customers activate the integration.
Test deployment and upgrades as well as execution
For code-native integrations, use local tests for transformation and recovery logic, then test in Prismatic for runtime behavior and connected services. For low-code integrations, use the testing drawer to inspect step results and exercise branches.
In either case, test an empty result, multiple pages, a duplicate event, an expired connection, and a failure after a successful write. Check the destination records after a retry as well as the execution status.
Test activation, reconfiguration, and deletion with a test customer's instance. Lifecycle functions can run again when an instance is redeployed, so setup should reuse or reconcile resources it already created. Check that cleanup removes only resources owned by that instance.
For a large sync, test a deployed instance with representative data: the batch trigger's test runner limits pagination and doesn't exercise a full backfill.
In code-native integrations, retain flow stable keys and config variable stable keys when renaming existing definitions. These keys let Prismatic match the renamed definitions to existing webhook URLs and configured values.
Before rolling out a new version, upgrade a test instance that already has configuration and sync state. Check whether new required settings, changed mappings, or a new state format need migration, and explain any customer action required by the release.