{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Paragon Documentation","colors":{"primary":"#6645e6","light":"#6747e7","dark":"#6645e6"},"logo":{"light":"https://mintcdn.com/paragon/VsFfcYgo5-Qm6khT/logo/light.svg?fit=max\u0026auto=format\u0026n=VsFfcYgo5-Qm6khT\u0026q=85\u0026s=348909bc26f6451e89f4010d965f6e3f","dark":"https://mintcdn.com/paragon/VsFfcYgo5-Qm6khT/logo/dark.svg?fit=max\u0026auto=format\u0026n=VsFfcYgo5-Qm6khT\u0026q=85\u0026s=94df13e90b5d4116e55d031d5583fa1b","href":"https://docs.useparagon.com/"},"favicon":"https://mintcdn.com/paragon/jHdUQ3U2jexiiVjS/favicon.png?fit=max\u0026auto=format\u0026n=jHdUQ3U2jexiiVjS\u0026q=85\u0026s=92cb5c619d517e9c751282890f36033e","api":{"playground":{"proxy":false}},"background":{"decoration":"gradient"},"navbar":{"links":[{"href":"https://dashboard.useparagon.com/signup","label":"Sign up"}],"primary":{"type":"button","label":"Book a demo","href":"https://www.useparagon.com/book-demo"}},"footer":{"links":[{"header":"Home","items":[{"label":"Home","href":"https://www.useparagon.com"},{"label":"Sign Up","href":"https://dashboard.useparagon.com/signup"},{"label":"Book Demo","href":"https://www.useparagon.com/book-demo"},{"label":"Pricing","href":"https://www.useparagon.com/pricing"},{"label":"Careers","href":"https://www.useparagon.com/careers"}]},{"header":"Resources","items":[{"label":"Blog","href":"https://www.useparagon.com/blog"},{"label":"Case Studies","href":"https://www.useparagon.com/customers"},{"label":"ActionKit Playground","href":"https://try.actions.useparagon.com"},{"label":"Sync Playground","href":"https://try.sync.useparagon.com"},{"label":"Product Tour","href":"https://www.useparagon.com/product-tour-landing"}]},{"header":"Legal","items":[{"label":"Privacy Policy","href":"https://www.useparagon.com/privacy-policy"},{"label":"Terms of Service","href":"https://www.useparagon.com/terms-of-service"},{"label":"Your Privacy Choices","href":"/optout"}]}]},"fonts":{"heading":{"family":"Helvetica Now Display Medium","weight":400,"source":"https://framerusercontent.com/assets/OKeqk0lRqQ5xtPOYZPiID295g.woff2","format":"woff2"}},"integrations":{"ga4":{"measurementId":"G-DH6VCLG1X6"},"gtm":{"tagId":"GTM-WCJFR4H"},"cookies":{"key":"paragon_docs_integrations_consent","value":"granted"}},"errors":{"404":{"redirect":false}},"styling":{"latex":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"paragon","hasAudioNarration":false,"actualSubdomain":"paragon","customDomains":["docs.useparagon.com"],"gitSource":{"type":"github","owner":"useparagon-internal","repo":"docs-mintlify","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ae3c95c97b1ae3164b1afaa","collectionId":"0d0c583a-8145-4f79-88fb-810e552b4378","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ae3c95c97b1ae3164b1afaa"},"feedback":{"edits":false,"thumbs":true},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"REMOVE_BRANDING":{"status":"ENABLED"},"CONTEXTUAL_FEEDBACK":{"status":"ENABLED"},"CODE_SNIPPET_FEEDBACK":{"status":"ENABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6ae3c95c97b1ae3164b1afaa","clientVersion":"0.0.4021","searchFilterCounts":[{"language":"default","tag":"ActionKit","count":1114},{"language":"default","tag":"Integrations Reference","count":1103},{"language":"default","tag":"Tools","count":863},{"language":"default","tag":"Triggers","count":147},{"language":"default","tag":"Integrations","count":126},{"language":"default","tag":"Integration Providers","count":126},{"language":"default","tag":"Managed Sync","count":67},{"language":"default","tag":"Platform","count":56},{"language":"default","tag":"Workflows","count":54},{"language":"default","tag":"Salesforce","count":51}]},"assistantConfig":{},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"#top-aligned-table td{vertical-align:top;padding:8px}#footer a[href='/optout']{overflow:auto;max-width:100%}footer>div{padding-top:4rem!important}a.nav-tabs-item[href=\"/resources/integrations\"]{margin-left:auto}div.nav-tabs{width:100%}"}],"page":{"pagePath":"guides","slug":"/guides","pageMetadata":{"title":"Guides","description":"Tutorials and knowledge base articles for building native integrations with Paragon.","mode":"custom","pagination":false,"href":"/guides"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Tutorials and knowledge base articles for building native integrations with Paragon.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];const GuidesHome=()=>{const _components={a:`a`,..._provideComponents()};const TOPIC_TITLES={authentication:`Authentication`,monitoring:`Monitoring`,\"deploying-integrations\":`Deploying Integrations`,\"custom-integrations-or-webhooks\":`Custom Integrations or Webhooks`,\"billing-and-account-management\":`Billing and Account Management`,\"managed-sync\":`Managed Sync`,actionkit:`ActionKit`,workflows:`Workflows`};const TOPICS=[{id:`authentication`,description:`Connect Portal, credentials, and why an integration shows connected but won't run.`,href:`/tutorials/building-an-in-app-integrations-catalog`},{id:`workflows`,description:`Build integration logic, and debug runs, triggers, and App Events.`,href:`/tutorials/building-a-salesforce-integration`},{id:`actionkit`,description:`Give agents integration tools and react to real-time events.`,href:`/actionkit/actionkit-for-tool-calling`},{id:`deploying-integrations`,description:`Git Sync, Releases, and shipping Paragraph changes.`,href:`/knowledge-base/git-sync-missing-commits`},{id:`custom-integrations-or-webhooks`,description:`Custom API requests and app-level webhooks.`,href:`/knowledge-base/api-base-url-ip-403`},{id:`billing-and-account-management`,description:`Team invites, Connected Users dates, and billing counts.`,href:`/knowledge-base/email-invite-404`}];const GUIDES=[{title:`Building an In-App Integrations Catalog`,description:`Use the Paragon SDK to build an integrations catalog into your React app.`,href:`/tutorials/building-an-in-app-integrations-catalog`,topic:`authentication`,tags:[`Connect SDK`,`UI`],featured:true},{title:`ActionKit for Tool Calling`,description:`Use ActionKit to give your AI agent hundreds of integration tools.`,href:`/actionkit/actionkit-for-tool-calling`,topic:`actionkit`,tags:[`ActionKit`,`Agents`],featured:true},{title:`Building a Salesforce Integration`,description:`Sync leads from your app to your customers' Salesforce accounts.`,href:`/tutorials/building-a-salesforce-integration`,topic:`workflows`,tags:[`Workflows`,`Salesforce`],featured:true},{title:`Run a Real-time Action`,description:`Build a real-time integration feature with ActionKit tools and triggers.`,href:`/tutorials/actionkit-guide/first-action`,topic:`actionkit`,tags:[`ActionKit`,`Triggers`],featured:true},{title:`Building a HubSpot Integration`,description:`Sync contacts from your app to your customers' HubSpot accounts.`,href:`/tutorials/building-a-hubspot-integration`,topic:`workflows`,tags:[`Workflows`,`HubSpot`]},{title:`Building a Slack Integration`,description:`Send notifications from your app to your customers' Slack workspaces.`,href:`/tutorials/building-a-slack-integration`,topic:`workflows`,tags:[`Workflows`,`Slack`]},{title:`Building a Marketo Integration`,description:`Sync leads from your app to lists in your customers' Marketo accounts.`,href:`/tutorials/building-a-marketo-integration`,topic:`workflows`,tags:[`Workflows`,`Marketo`]},{title:`Building a BambooHR Integration`,description:`Sync employees from your app to your customers' BambooHR accounts.`,href:`/tutorials/building-a-bamboohr-integration`,topic:`workflows`,tags:[`Workflows`,`BambooHR`]},{title:`Subscribe to Real-time Events`,description:`Subscribe to an ActionKit Trigger that sends a webhook when a HubSpot record is created.`,href:`/tutorials/actionkit-guide/first-trigger`,topic:`actionkit`,tags:[`ActionKit`,`Triggers`]},{title:`Full Example of an End-to-End HubSpot Integration`,description:`Tie tools and triggers together in a reactive HubSpot agent.`,href:`/tutorials/actionkit-guide/reactive-integration-example`,topic:`actionkit`,tags:[`ActionKit`,`HubSpot`]},{title:`AI Agent Playground`,description:`A sandbox for chatting with an agent that has access to ActionKit tools.`,href:`/actionkit/actionkit-playground`,topic:`actionkit`,tags:[`ActionKit`,`Agents`]},{title:`Quickstart Sample App`,description:`Production-ready Next.js example with the Connect SDK.`,href:`https://github.com/useparagon/paragon-connect-nextjs-example`,topic:`authentication`,tags:[`Connect SDK`],external:true},{title:`Paragon MCP Server`,description:`Connect ActionKit tools to MCP-compatible agents.`,href:`https://github.com/useparagon/paragon-mcp`,topic:`actionkit`,tags:[`ActionKit`,`MCP`],external:true}];const KB_SEARCH_INDEX={\"articles\":[{\"title\":`A Paragraph push or Release cleared client IDs and secrets`,\"description\":`Client IDs, secrets, and environment secrets are dashboard-only. They go missing when a Release treats an integration as an Add.`,\"href\":`/knowledge-base/paragraph-cleared-client-secrets`,\"topic\":`deploying-integrations`,\"tags\":[],\"keywords\":[],\"body\":`If OAuth client IDs, client secrets, or environment secrets are empty after para push or a Release, Paragraph did not copy those values out of git. They are dashboard-only. A normal pull and push of the same project leaves them in place. They disappear when the platform treats the integration as new (a delete plus create), which the Release preview labels Add, not Update. Check the Release Change Summary before you deploy. If integrations you thought were existing show as Adds, stop and restore identity; do not deploy. Symptoms After para push and a Release (Development -> Staging -> Production), some integrations have a blank client ID / client secret. Others in the same Release are unchanged. You restored integrations from git history after they were deleted from the Paragraph project. The Release preview listed those integrations as Adds, not Updates. You may also have seen New Integration Requires Additional App Configuration. Integration and workflow IDs changed, or connected users for that integration were wiped. Integration Removed deletes connected accounts and workflows for a removed integration. You will not see Paragraph files that contain the missing secrets. Getting Started lists client ID and secret, environment secrets, signing keys, and (for catalog integrations) OAuth scopes as dashboard-only. Cause Paragraph stores workflow and integration logic. It does not store OAuth app credentials or environment secrets. para pull / para push and Git Sync therefore have nothing to write into those dashboard fields on a stable integration. Build and Push does say that integrations or workflows absent from the source are removed from the destination. If someone deletes an integration folder in git, a push removes it from the project. Restoring the files from an old commit can bring back code whose IDs no longer match the live project. The next Release then compares by identity and sees Adds (create) instead of Updates (same integration, new logic). | Release change type | What happens to credentials | | --- | --- | | Update / Modify | Client ID, secret, scopes, and environment secrets on that integration stay. Paragraph never overwrote them. | | Add | This is a new integration in the target environment. OAuth app config is empty until you paste it. The Release warning for additional app configuration is the prompt to do that. | | Remove | The integration is deleted. Connected accounts and its workflows go with it. | That is why the wipe looks intermittent: integrations that stayed Updates keep their secrets; integrations that were Adds do not. Fix 1. Read Add vs Update before you deploy In the undeployed Release Preview, every integration row is Add, Modify, or Remove. Adds that you did not intend mean identity was lost. Cancel the Release, fix Development, and preview again. 2. Do not restore a deleted integration from git history and Release it as-is If the folder was deleted and you need the integration back, recreate it so the project keeps a current identity, or pull from the live dashboard (para pull) and re-apply logic on top of those IDs. Pushing an old tree so the platform \"adds\" seven integrations will mint seven empty credential slots. 3. If you already deployed Adds, paste credentials in the dashboard Open each new integration's Settings and set client ID, secret, and scopes. Re-enter environment secrets if the Release warned they were missing. Connected users for a removed-then-added integration must reconnect. Optional check to validate Add vs Update: call Get integration metadata before and after a pull/push. If IDs changed, you recreated the integration. Prevention Treat \"absent from git\" as a delete of the live integration, per Build and Push. On every Release, scan the Change Summary for unexpected Adds and Removes before Deploy. Keep client ID, secret, and environment secrets only in the dashboard (and your own secret store). Do not expect git to round-trip them. Still having issues? Send the Release Change Summary (Add/Modify/Remove per integration), whether you restored files from git history, and whether integration IDs changed around the deploy. Related articles What isn't defined in a Paragraph project Build and Push Release Environments Setting up Git Sync`},{\"title\":`ActionKit accepts unknown parameters and ignores them`,\"description\":`Run Tool returns 200 and silently drops keys that aren't on the tool schema, which can leave a search unfiltered.`,\"href\":`/knowledge-base/actionkit-unknown-parameters`,\"topic\":`actionkit`,\"tags\":[`ActionKit`,`Workflows`],\"keywords\":[`unknown parameters`,`filter ignored`,`schemaEnforcement`,`Run Tool`,`additionalProperties`],\"body\":`If an ActionKit call returns 200 but the filter, limit, or field you passed did nothing, Run Tool likely ignored those keys. Extra or misspelled parameters are not rejected. The action runs with whatever documented fields were actually present, which for a search is often the unfiltered default. Compare your parameters object to the tool schema from List Tools or the Tools Reference. Drop keys that are not in that schema. There is no schemaEnforcement query parameter on Run Tool. Symptoms - The request succeeds. You do not get a validation error naming the bad fields. - A search or list action returns a large, unfiltered set. Filters you thought you sent (assignee_id, status_filter, a similar invented name) have no effect. - An LLM is calling a generic run_integration_action (or passing List Tools schemas loosely) and invents parameter names that look reasonable. - Task History shows a successful ActionKit run. For successful runs, the dashboard does not show the provider response body, so the only clue is that the result set does not match the intent. You will not see a 4xx that lists unknown properties. Cause Run Tool takes tool plus a parameters object. Each tool's documented fields are listed on its Tools Reference page and in the JSON Schema from List Tools. Run Tool does not currently fail the call when parameters contains keys outside that schema. Unknown keys are dropped. Missing optional keys use the tool's defaults. The HTTP response is still success, so an agent or a thin wrapper has no error to feed back to the model. That is especially visible on filter actions: if the real filter field is absent, the action searches with no filter. Worked example: Filter Issues (JIRA_FILTER_ISSUES) documents jqlQuery (DNF object with operator and conditions) and limit. This body is accepted and does not filter: { \"action\": \"JIRA_FILTER_ISSUES\", \"parameters\": { \"assignee_id\": \"me\", \"max_results\": 50, \"status_filter\": \"in_progress\" } } None of those keys are on the schema. jqlQuery is omitted, so you get the default issue list. Use jqlQuery for assignee and status, and limit (not max_results) to cap the page. For a raw JQL string, use Search By JQL (JIRA_SEARCH_BY_JQL) instead. The same pattern applies to every tool: the names in parameters must match that tool's schema, not a guess, not another product's API, and not a sibling tool's fields. | What you pass | What Run Tool does | | -------------------------------------------------------------- | --------------------------------------------------------------------------- | | Only keys from the tool schema | Those fields are applied | | Extra keys that are not on the schema | Call still succeeds; extra keys are ignored | | Wrong name for a real field (max_results instead of limit) | The real field is treated as absent (default or unfiltered) | | Required field missing | Follows that tool's required list; this is separate from extra-key handling | Fix 1. Diff the payload against the schema Pull the tool from List Tools (format=json_schema) or open its Tools Reference page. Every key under parameters must appear there. If you wrap many tools in one generic function, validate the model's arguments against that tool's schema before calling Run Tool. Return the validator's errors to the model. ActionKit will not do that for you. 2. Put filters in the documented filter field Do not invent assignee_id, status_filter, or similar. Use the filter parameter the schema names (jqlQuery, filterFormula, a COQL query on a dedicated query tool, and so on). 3. Cap result size with the documented limit field Large unfiltered responses are a consequence of a no-op filter, not a separate ActionKit bug. Use the tool's limit / paginationParameters fields, and handle large payloads in your app if the default page is still too big. Prevention - Pass List Tools JSON Schema into the model as the tool definition, as in ActionKit for tool calling, instead of a single unconstrained run_integration_action. - Reject extra properties in your own validator (additionalProperties: false on the tool's parameters schema). - Treat HTTP 200 as \"the tool ran,\" not \"every key you sent was applied.\" Still having issues? Send the exact Run Tool body (tool name and parameters), the tool schema you used, and a short description of what you expected vs what came back. If the keys match the schema and the provider still ignores the filter, that is a different problem than this one. Related articles - Run Tool - List Tools - ActionKit for tool calling - Schema formats - JIRA_FILTER_ISSUES`},{\"title\":`Date Created on Connected Users is older than the first integration connection`,\"description\":`Date Created records when Paragon first saw a user, not when they connected an integration.`,\"href\":`/knowledge-base/connected-users-date-created`,\"topic\":`billing-and-account-management`,\"tags\":[`Billing`,`Connected Users`],\"keywords\":[`Date Created`,`Connected Users`,`authenticate`,`billing`],\"body\":`If Date Created on the Connected Users list looks months earlier than when the user connected an integration, the date is probably right. It records when Paragon first heard from that user, not when they connected Salesforce, HubSpot, or any other app. Jump to What the two dates mean. Symptoms - You know a user connected an integration recently. - The Connected Users list shows Date Created weeks or months earlier. - It can look like every row is \"wrong,\" so the dashboard seems broken. - Billing or usage questions often come with it: \"have we been charged for this user since that older date?\" Cause Paragon creates a user record the first time your application talks to Paragon for that person. That usually happens when your app calls paragon.authenticate(...), or when it sends a REST request with their user token. The Connect SDK is meant to authenticate at the start of the user's session, so this often happens the first time they use your product after you shipped Paragon, not the first time they click Connect on an integration. That record can sit with no integrations at all. Those users are hidden on the default Connected Users list. They appear once they connect an integration, still carrying the original Date Created. A billed Connected User is a user ID that has connected at least one integration. Creating the record on authenticate does not, by itself, count against that limit. What the two dates mean | What you are looking at | What it is | Where to see it | | ----------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Date Created | When Paragon first created this user (first authenticate or REST call) | The Connected Users list | | Integration connected | When this user connected a specific app account | The user's profile: open the user, open the integration, open the account dropdown | | Billed Connected User | The user has at least one integration connected | Counts in the header of the Connected Users dashboard | The list can only show one date per user. If they later connect a second integration, Date Created still cannot be \"the connection date,\" because there would be two. Fix Nothing is out of sync. Use the date that matches the question you actually have. When was this person first set up in Paragon? Use Date Created on the list. When did they connect this integration? Open Connected Users, select the user, open the integration panel, then open the account dropdown. That is the connection for that app. If the user has more than one account for the same integration (multi-account), each account is listed there. Were they on the billed count before they connected anything? No. Users with no integrations do not count. You can still find those records: on Connected Users, filter Integrations Enabled > No Integrations. If Date Created is much earlier than you expected, check when your application first calls paragon.authenticate for that user. A call on every login will create the record the first time they show up, even if they never open the Connect Portal. Prevention When you read Date Created, treat it as \"first seen by Paragon,\" not \"first connected an integration.\" If you are auditing connections or billing, go to the account dropdown on the user profile, and use the billed count in the dashboard header rather than scanning list dates. Still having issues? If the account dropdown date is also wrong, or the user never called authenticate that early, send support: - The User ID from the Connected Users list - Date Created as shown - Which integration you are comparing, and when you believe it was connected - Whether this user had any other integrations before that Related articles - Managing Connected Users - Connected Users (billing) - Installing the Connect SDK`},{\"title\":`Error: 403 Forbidden when the API Base URL is an IP address`,\"description\":`Paragon blocks outbound requests to raw IP addresses. Use a hostname for the API Base URL.`,\"href\":`/knowledge-base/api-base-url-ip-403`,\"topic\":`custom-integrations-or-webhooks`,\"tags\":[],\"keywords\":[],\"body\":`If Send test request (or another outbound request) returns 403 Forbidden and the Response tab is a short HTML page, this article explains why and how to fix it. Paragon does not send requests to IP addresses. Use a hostname. Jump to Fix. Symptoms Send test request (or another outbound request) fails. The Response tab shows a generic error page: 403 Forbidden 403 Forbidden The same URL works from curl, Postman, or a browser. Logging out and back in does not change the result. The API Base URL (or request URL) looks like http://203.0.113.10:8000/... or another raw IPv4/IPv6 address, not https://api.example.com/.... You will not see a Paragon error that says \"IP addresses are not allowed.\" The 403 is the entire signal. Cause Paragon blocks outbound requests whose host is an IP address. That policy applies across request URLs, not only Custom Integrations. The 403 is returned by Paragon before the request reaches the provider, which is why the same call succeeds from another client. This is easy to misread as an auth failure. A wrong Authorization header or an expired dashboard session can also produce errors, but they are not this HTML 403. Fix 1. Give the host a DNS name that resolves to that machine (or put a reverse proxy with a hostname in front of it). 2. Set the Base URL to that hostname, including the scheme and port if needed. Example: https://api.example.com:8000/ or https://api.example.com/. 3. Set Test Endpoint URL to a path that accepts GET, relative to that base URL. 4. Click Send test request again. Do not substitute localhost or another IP. The host in the URL must be a domain name. Prevention When you configure an API Base URL, Test Endpoint URL, Request step URL, or API Resource base URL, use a hostname. If the provider is only reachable by IP today, add DNS (or a hostname on a load balancer) before wiring it into Paragon. Still having issues? If the 403 persists after the URL uses a hostname, it is a different failure (provider ACL, TLS, or an allowlisted-host error). Send support: The exact API Base URL and Test Endpoint URL (hostname vs IP) The full Response tab body Whether the same URL succeeds from another client Related articles Custom Integrations Request steps API Resources`},{\"title\":`Git Sync CI succeeded but the dashboard does not show my GitHub commits`,\"description\":`The push action updates the Paragon project; it never commits back to GitHub. Where to look instead.`,\"href\":`/knowledge-base/git-sync-missing-commits`,\"topic\":`deploying-integrations`,\"tags\":[],\"keywords\":[],\"body\":`If paragraph-push-action (or para push) succeeded and GitHub looks fine, but the Git Sync panel does not list that commit, the push updated the Paragon project. It did not write a new commit back to GitHub, and the panel only lists Git Sync action runs. Confirm the change in the Development dashboard (workflows and integrations), not in the Git Sync commit list. To get dashboard edits into GitHub you need the pull GitHub Action. Do not connect Development and Production to the same repo and branch. Symptoms A merge to your Git Sync branch runs Push to Paragon successfully, but the Git Sync history in the dashboard does not show that GitHub commit. The commit appears under Production (or the other project you connected to the same branch), not under Development. You expected GitHub to receive a Paragon-generated commit after the CI push. It did not. Workflows in Production do not match the PR. That is expected if you only ran Git Sync push. Production is updated with a Release, not with para push. Cause Git Sync is two separate pipelines. Setting up Git Sync documents both: | Direction | What runs | What you should see | | --- | --- | --- | | Git -> Paragon | .github/workflows/paragraph-push.yml (paragraph-push-action) | Changes in the linked Paragon project's dashboard | | Paragon -> Git | .github/workflows/paragraph_pull_runner.yml (paragraph-pull-action) | A new commit on the connected branch | | Development -> Staging -> Production | A Release | Production workflows match Development after you deploy | paragraph-push.yml only builds and pushes Paragraph into Paragon. It never commits to GitHub. If GitHub is your source of truth and you omitted the pull workflow, there will be no Paragon -> GitHub commits. That is one-way Git -> Paragon, not a failed sync. The Git Sync status UI lists those action runs. It is not a clone of git log on the branch. Ordinary GitHub commits that never ran the push or pull action do not appear there. Two more mapping rules cause the \"wrong place\" symptom: 1. para push cannot update Staging or Production. Build and Push says workflows cannot be pushed to those environments. Create a Release instead. You can still attach Git Sync to a Production project for a read-only export, but the platform will not apply GitHub pushes there. 2. One repo and branch should map to one Paragon project. Each project has its own IDs. Development and Production both pointing at master (or main) collide. The push can land in Development while the Git Sync history for that commit shows on Production, or the two projects fight over the same files. Fix 1. Check the dashboard, not only Git Sync history Open the project that paragraph-push.yml is meant to update (Development, if you are using Releases). If the workflow or integration change from the PR is there, the push worked. 2. Use one Git Sync mapping for the writable project Connect Git Sync to Development only when GitHub is the source of truth for code you will Release upward. If Production is still connected to the same repo and branch, disconnect it. Resetting the repository requires contacting support; you cannot always clear the mapping from the UI. 3. Add the pull workflow only if you want Paragon -> GitHub Copy paragraph_pull_runner.yml from GitHub Actions Workflow Files. Dashboard Save Version / Git Sync events use that action to commit a diff. Skip it if you do not want dashboard edits written back to the repo. 4. Promote to Production with a Release After Development has the Paragraph update, create a Release Development -> Staging -> Production. Do not expect the GitHub Action to deploy Production. Prevention Treat paragraph-push.yml as deploy-to-Development (or to a single non-Release project), not as \"mirror GitHub into the Git Sync panel.\" Do not attach two Paragon projects to the same repository and branch. If GitHub is the only source of truth, omit the pull action on purpose, and do not look for Paragon-authored commits. Still having issues? Send the GitHub Action run URL, which Paragon projects have Git Sync connected (and to which repo/branch), whether paragraph_pull_runner.yml exists, and whether the dashboard change is visible in Development. Related articles Setting up Git Sync Build and Push Release Environments Getting Started with Paragraph`},{\"title\":`Headless install stalls after OAuth with a pending credential`,\"description\":`A PENDING credential makes the next installFlow.start resume post-OAuth options instead of opening a new OAuth window.`,\"href\":`/knowledge-base/headless-install-pending-credential`,\"topic\":`authentication`,\"tags\":[],\"keywords\":[],\"body\":`If Connect looks successful once, then later clicks never open OAuth and the integration stays disconnected in your UI, the user likely still has a PENDING credential. That happens when OAuth finishes but required post-OAuth options are not set. The next installFlow.start resumes that incomplete install instead of starting a new one. This is not a blocked popup. If onError reports OAuthBlockedError, use OAuth popup is blocked when connecting an integration. Symptoms First Connect appears to complete OAuth, then the integration never shows as enabled. Dashboard or getUser() shows credentialStatus: \"PENDING\" (sometimes labeled pending configuration). Later Connect clicks do not open an OAuth window. The Headless flow goes straight to postOptions, or appears stuck with no UI. onComplete never fires. Your onNext handler may call setPostOptions({}) and return. Cause Headless install is a sequence of stages. OAuth is only one of them. After OAuth, some integrations emit PostOptionsStage (a site, workspace, or similar required field). setPostOptions saves those values and completes the connection. If you skip the stage or pass an empty object, Paragon can still create a credential, but it stays PENDING because required post-OAuth options were never provided. The next installFlow.start for that user and integration prefers completing the pending credential. You never get a new OAuth popup, so it looks like Connect is broken. | onNext for postOptions | What Paragon stores | Next Connect | | --- | --- | --- | | Collect the required fields and call setPostOptions({ FIELD_ID: value }) | credentialStatus becomes usable (VALID when nothing else is wrong) | New installs work as usual | | setPostOptions({}) or no handler | Credential stays PENDING | Start resumes postOptions; no new OAuth window | | User closes the tab mid-stage | Credential may stay PENDING | Same resume behavior until you cancel or uninstall | installFlow.cancel documents this: if OAuth completed but required post-OAuth options did not, cancel removes the PENDING credential. After a reload, call uninstallIntegration yourself. Fix 1. Confirm the credential is PENDING const user = paragon.getUser(); const integration = user.integrations[\" \"]; if (integration?.credentialStatus === \"PENDING\") { // This Connect attempt will resume post-OAuth options, not a new OAuth popup. } 2. Complete post-OAuth options in onNext Handle postOptions the same way you handle preOptions: render the fields from next.options and pass the selected values into setPostOptions. The SDK example uses field ids from that stage (for example JIRA_CLOUD_ID). 3. Clear a stuck PENDING credential 1. If the install flow is still in memory, call paragon.installFlow.cancel(). That uninstalls a PENDING credential created in the current session. 2. If the page reloaded, call paragon.uninstallIntegration(\" \") (and selectedCredentialId when using multiple accounts). 3. Start installFlow.start again, with a real postOptions handler. Prevention Implement PostOptionsStage for every integration you enable. A handler that only works for integrations without post-OAuth fields will fail the first time you add one that has them. After onComplete, check getUser() and refuse to treat PENDING as connected. If the user abandons Connect after OAuth, call installFlow.cancel() so they do not remain PENDING. Related articles Headless Connect Portal installFlow.setPostOptions installFlow.cancel OAuth popup is blocked when connecting an integration`},{\"title\":`How to map an app-level webhook to the right Connected Users`,\"description\":`Set Provider ID so one app-level Custom Webhook runs only for the users it belongs to, instead of everyone with a blank providerId.`,\"href\":`/knowledge-base/app-level-webhook-provider-id`,\"topic\":`custom-integrations-or-webhooks`,\"tags\":[],\"keywords\":[],\"body\":`This guide shows you how to use Provider ID so one app-level Custom Webhook runs only for the Connected Users it belongs to. If you leave Provider ID blank, one inbound event can start a workflow for every user whose providerId is empty. If you are already seeing one webhook create dozens of executions, jump to Why a blank Provider ID runs for everyone. Prerequisites A Custom Webhook (or API Resource Trigger) whose type is App-level. Custom Webhooks require a Pro plan or above. For a custom integration: you can edit Step 4. API Setup and the User Profile section. The inbound event includes a stable ID you can match (user id, account id, tenant id). Why a blank Provider ID runs for everyone App-level webhooks are registered once. Paragon then decides which Connected Users should run by matching a field on the event to each user's Provider ID (Event Routing). | Provider ID on the trigger | Who runs (among users who have the workflow enabled) | | --- | --- | | A unique value from User Profile / providerData | Only users whose stored Provider ID equals the ID in the event | | Empty / blank | Every user whose providerId is also empty | Blank matching is the wrong default if you switched to app-level to register one URL. You get one URL, and also one execution per matching user, every time the provider fires. That fan-out has three costs: Wrong-account work. If routing inside the workflow is wrong, steps run in the wrong user's connection. Task History noise. Five events and twenty blank-providerId users is 100 executions. Finding the one that failed is slow. Queue. The same math fills the execution queue. At small N it is fine. It does not stay small. Do not leave Provider ID blank to \"match everyone and filter later\" unless you have accepted those costs. Set User Profile so each connection has a real Provider ID. Step 1: Save a unique ID on User Profile In the Custom Integration wizard, open Step 4. API Setup. At the bottom of the left sidebar, open User Profile. Configure an API Request (typical: a users/me style endpoint) or a JWT Decode so Paragon stores identifying fields on the connection. Those fields are available later as providerData on the user (Custom Integrations, getUser). Use a value that also appears on inbound webhook events. If the provider has no profile endpoint, you need some other unique field you control at connect time. Do not rely on an empty providerId. Existing connections do not pick this up automatically. Users who connected before you saved User Profile must disconnect and reconnect. They will need to fill in Connect Portal settings again. Step 2: Match the event field to Provider ID In the Custom Webhook wizard, Step 3. Event Routing: 1. Point the event-side ID at the payload (or header) field that identifies the tenant. 2. Set Provider ID to the User Profile path, for example {{profileData.user.user_id}}. Use the path that matches your providerData shape. Paragon runs the workflow for each Connected User whose stored Provider ID equals that event field, and who has the workflow enabled. Comparison is not strict about number vs string: 134450 matches \"134450\". Prefer storing and sending the same type anyway. Step 3: Register one webhook URL Keep the trigger App-level. Register the Webhook Target URL once in the provider's app settings, as in Custom Webhooks, Manual Setup. You should not register a per-user URL for this trigger. Verify it worked 1. Connect at least two test users with different User Profile IDs, and enable the workflow on both. 2. Send an event whose ID matches only user A. 3. Task History should show executions for A (and any other user who shares that Provider ID), not for B. 4. Confirm providerId is non-empty on paragon.getUser() / GET /sdk/me for those connections. Common issues | What you see | Cause | Fix | | --- | --- | --- | | One webhook, N executions, one per Connected User | Provider ID is blank and those users have empty providerId | Set User Profile, set Provider ID to {{profileData...}}, reconnect users | | No executions | The event field does not equal any user's Provider ID, or the workflow is not enabled | Compare the payload ID to /sdk/me providerId / providerData | | Test Connect Portal user has no providerData in the variable menu | The test user connected before User Profile was saved, or the profile request never ran | Reconnect that test user after User Profile is configured | | Users connected before Step 4 still have providerId: \"\" | User Profile is not retroactive | Disconnect and reconnect those users | | Disconnect/reconnect wipes Connect Portal settings | Expected for a full reconnect | Plan to restore User Settings after reconnect | Still having issues? Send support: Whether the trigger is App-level or User-level The Event Routing fields (event ID path and Provider ID / profileConfigKey path), and whether Provider ID is blank How many Connected Users have this workflow enabled, and how many of them have empty providerId One Task History ID from a fan-out burst, and how many executions that single inbound event produced A redacted /sdk/me snippet showing providerId and providerData (no tokens) Related articles Custom Webhooks Custom Integrations API Resource Trigger getUser`},{\"title\":`How to send an App Event to a single configuration`,\"description\":`Pass X-Paragon-Credential and X-Paragon-Configuration-Id so one event runs for one Multi-Configuration instance.`,\"href\":`/knowledge-base/app-event-single-configuration`,\"topic\":`workflows`,\"tags\":[],\"keywords\":[],\"body\":`This guide shows you how to send an App Event so it runs for one Multi-Configuration instance, not every configuration that has the workflow enabled. If you are already seeing duplicate Task History rows from one event, start with Workflow runs twice from one App Event or integration trigger. Prerequisites Multi-Configuration is enabled for the integration. The Connected User has at least one connected account (credential) and at least one configuration. You can mint a Paragon User Token for that user (installing the Connect SDK). Why the App Events example runs more than once The App Events request is untargeted. Paragon accepts the event (201) and starts every matching workflow for that user, including every configuration that has the workflow enabled. That is the right default when each configuration should handle the event with its own settings. It is the wrong default when you meant \"create this record in one project / one folder / one team.\" To pin the event to one configuration, send the credential UUID and the configuration ID on the request. Step 1: Read the credential and configuration IDs Call paragon.getUser() or: GET https://api.useparagon.com/projects/ /sdk/me Authorization: Bearer Under integrations. you get: allCredentials: connected accounts. Each has an id (the credential UUID) and a configurations array. allConfigurations: the configurations for that integration. Each configuration includes: id: Paragon's configuration UUID. You can send this as X-Paragon-Configuration-Id as-is. externalId: the ID you passed to createConfiguration, if any. Send it as ext: plus that value, for example ext:141. A credential UUID for the parent connected account (the same value as the parent credential's id). Send that as X-Paragon-Credential. The configuration UUID is stable for the life of that configuration. The credential UUID is stable for the life of the connected account; it changes if the user disconnects and reconnects. If you store IDs in your database, refresh them from /sdk/me after a disconnect/reconnect so you do not keep a stale credential UUID. Step 2: Send the App Event with both headers POST https://api.useparagon.com/projects/ /sdk/events/trigger Authorization: Bearer Content-Type: application/json X-Paragon-Credential: X-Paragon-Configuration-Id: { \"name\": \"Contact Created\", \"payload\": { \"name\": \"Brandon\", \"email\": \"email address\" } } X-Paragon-Configuration-Id accepts a configuration UUID, or an external ID with the ext: prefix. Do not send the bare external ID. Send both headers. Sending only X-Paragon-Configuration-Id can return 400 Bad Request. The same request without either header returns 201 and fans out. The SDK equivalent is selectedConfigurationId on paragon.event. See Calling the API. For a request that must name both a credential and a configuration, use the REST call above. Verify it worked The App Event endpoint responds 201. Task History shows one execution of the workflow for that user, for the configuration you targeted, even if other configurations still have the workflow enabled. Common issues | What you see | Cause | Fix | | --- | --- | --- | | Two or more executions, identical payloads, no error | The event was untargeted and more than one configuration has the workflow enabled | Add both headers. See Workflow runs twice from one App Event or integration trigger. | | 400 Bad Request after adding X-Paragon-Configuration-Id | Missing X-Paragon-Credential, or a configuration ID that is not a UUID and not ext:-prefixed | Send both headers. Use a configuration UUID or ext: plus your external ID. | | 201 but still N executions | The configuration ID did not match, or you targeted a configuration that is not the only one you intended | Confirm the IDs against /sdk/me. Remember ext: on external IDs. | | Stale credential UUID after reconnect | The user disconnected and connected again; Paragon issued a new credential UUID | Read /sdk/me again and update what you store. | Still stuck? Send support the request URL (not an on-prem custom hostname unless you are on-prem), the two header names and whether each was present, the exact X-Paragon-Configuration-Id value (UUID vs ext: vs bare external ID), the HTTP status, and the Task History IDs. Related articles Workflow runs twice from one App Event or integration trigger Multi-Configuration App Events getUser / GET /sdk/me`},{\"title\":`Integration shows connected, but workflows are not running`,\"description\":`A credential can stop working while credentialStatus still reads VALID. How to check it and prompt a reconnect.`,\"href\":`/knowledge-base/integration-connected-workflows-not-running`,\"topic\":`authentication`,\"tags\":[`Workflows`,`Credentials`],\"keywords\":[`credentialStatus VALID`,`INVALID`,`workflows not running`,`reconnect`],\"body\":`If an integration appears connected but its workflows are not triggering, the saved credential may no longer be usable even though its last known status is still VALID. Check the credential status and monitoring logs, then ask the user to reconnect if Paragon has marked the credential INVALID. Symptoms - The integration appears connected in your app or the Connect Portal. - Enabled workflows stop triggering or show no recent executions. - Opening integration settings produces connection errors. - paragon.getUser() returns credentialStatus: \"VALID\", but a request to the provider fails. - After another attempt to use the connection, the status changes to INVALID or the Connect Portal displays a reconnect warning. Cause credentialStatus is Paragon's last known credential state. A credential can expire or be revoked outside Paragon, and Paragon cannot detect that change until it attempts to use or refresh the credential and receives a failure that indicates the connection cannot recover. This is why an inactive integration can continue to appear connected after its credential becomes unusable. Normal API errors do not always mean a credential is invalid. Paragon must distinguish a permanent credential failure from a temporary error, permissions error, or rate limit. Marking a credential INVALID does not delete the user's connection. It marks the connection as requiring attention, stops it from being treated as enabled, and allows the Connect Portal to prompt the user to reconnect. Fix 1. Check the current credential status Call paragon.getUser() and inspect the integration's credentialStatus: const user = await paragon.getUser(); const integration = user.integrations[\" \"]; if (integration?.credentialStatus === \"INVALID\") { // Show a prompt asking the user to reconnect. } The Users API documentation recommends showing a reconnection prompt whenever credentialStatus is not \"VALID\". 2. Review credential and workflow logs In the Paragon dashboard, open Monitoring > All Logs. Filter by the affected User ID and Integration ID, then look for: - Failed credential refresh events - Workflow failures caused by authentication errors - The most recent successful workflow or credential refresh Credential refresh events are currently available for select integrations. An absence of credential events does not prove the credential is valid. Monitoring > All Logs is the right place for credential refresh events (attempted / failed / succeeded) for a specific user and integration, but All Logs doesn't show workflow runs. Workflow executions and step-level auth errors are under Monitoring > Workflows. 3. Reconnect an invalid credential If credentialStatus is INVALID, prompt the user to reconnect through the Connect Portal. For a Headless Connect Portal, start the same installation flow used for the original connection. In a multi-account integration, pass the affected credential ID so the correct account is replaced. Do not remove the connection solely because it is invalid. Reconnecting preserves the user's intent to use the integration. If the user wants to remove it instead, follow the Users API instructions for disconnecting integrations. 4. Contact support if the status remains VALID If the provider rejects requests but credentialStatus remains VALID, send Paragon Support: - Project ID - Connected User ID - Integration type - Credential ID, for multi-account connections - Workflow execution or request ID - Exact provider error and timestamp - The last known successful execution This information helps determine whether the provider returned a temporary error, a permanent credential failure, or an error Paragon did not classify correctly. Prevention Notify your team about credential failures Configure a Credential Failure Event Destination. The event is sent when an OAuth refresh fails in a way that cannot be retried and the credential is marked invalid. Use the event's User ID and integration type to notify the affected user to reconnect. Webhook Event Destinations are available on paid plans and must be configured by a project Admin. You can also configure Workflow Failure notifications to catch scheduled work that fails after a connection problem. Credential Failure events report failures after they are detected. They do not predict when a provider will revoke a token or when a customer-managed secret will expire. Related articles - Get Connected User info and integration state - Managing Event Destinations - Event Logs - Headless Connect Portal`},{\"title\":`OAuth popup is blocked when connecting an integration`,\"description\":`Browsers block window.open once the click gesture expires. Keep awaits out of the Connect handler.`,\"href\":`/knowledge-base/oauth-popup-blocked`,\"topic\":`authentication`,\"tags\":[],\"keywords\":[],\"body\":`If the OAuth window never appears after Connect, the browser likely blocked window.open. Browsers only allow that call while the user's click is still considered active. Anything that delays the popup until after that gesture expires can make the browser treat it as unsolicited. When this happens, you can expect an OAuthBlockedError in the onError callback of paragon.connect. Then look for work that runs between the click and window.open, so installFlow.start (or connect / installIntegration) still happens in the click handler. Symptoms Clicking Connect does nothing, or a spinner never ends. Chrome or Safari shows a blocked-popup indicator. Connect SDK onError receives: { name: \"OAuthBlockedError\", message: \"Popup was blocked by the browser.\", meta: null } Cause The OAuth prompt uses a popup. Chrome and Safari allow window.open when it runs in the same turn as a user gesture. They block it when the gesture has expired. The delay can come from several places. One of them is calling paragon.authenticate inside the click handler. Others include lazy-loading the SDK on click, awaiting a JWT, analytics, or a slow network after the click. A handler that stacks several of those steps looks like this: button.onclick = async () => { const { paragon } = await import(\"@useparagon/connect\"); await paragon.authenticate(projectId, userToken); paragon.setHeadless(true); paragon.installFlow.start(\"salesforce\", { onError, onNext, onComplete }); }; That sequence is: click, then load the SDK, then authenticate, then open OAuth. The last step is no longer attached to the click. Any one of those awaits is enough to lose the gesture. | Common scenarios | Outcome | | --- | --- | | Dynamic import of @useparagon/connect on click | Often blocked | | await paragon.authenticate(...) on click (one possible cause) | Often blocked | | Analytics, feature flags, or extra network calls before start | Often blocked | | Slow network after click (you can reproduce this with DevTools throttling) | Often blocked | Fix 1. Confirm it is a popup block In a Headless Connect Portal, inspect error.name in onError. If it is OAuthBlockedError, this article applies. If you are using the hosted Connect Portal, check the browser's popup indicator on your app origin. 2. Keep the Connect click free of awaits Start the install in the click handler with no async work in front of it. If you currently authenticate on click, move paragon.authenticate to page load or app start instead. Displaying the Connect Portal already recommends that for token freshness. It also happens to keep authenticate off the click path. // Page load, not the Connect click await paragon.authenticate(projectId, userToken); paragon.setHeadless(true); // Connect click: no awaits before start connectButton.addEventListener(\"click\", () => { paragon.installFlow.start(\"salesforce\", { onNext, onComplete, onError: (error) => { if (error.name === \"OAuthBlockedError\") { // Tell the user the popup was blocked, and that they can retry after allowing popups for your origin. } }, }); }); Apply the same rule to other delays: load @useparagon/connect before the click, and do not await analytics or a JWT fetch before start / connect / installIntegration. Keep onNext handlers that collect pre-OAuth inputs synchronous with the next user click. Calling setPreOptions from that second click is a new gesture, so the OAuth popup that follows it is still allowed. 3. Surface the error instead of spinning OAuthBlockedError is thrown so you can replace an infinite spinner with a retry message. Related articles Headless Connect Portal installFlow.start Displaying the Connect Portal`},{\"title\":`Paragon email invites return a 404 or can't be accepted`,\"description\":`Why a team invite fails when the email already belongs to another Paragon account, and how to free it.`,\"href\":`/knowledge-base/email-invite-404`,\"topic\":`billing-and-account-management`,\"tags\":[`Account`,`Team`],\"keywords\":[`404 invite`,`Join Now`,`email already in use`,`team invite`],\"body\":`Problem Paragon email addresses must be unique across all Paragon accounts, not just within one team. If the invited person already created a Paragon account (including a trial) with that same email, clicking Join Now in a new team invite can fail. The dashboard often shows a generic 404 instead of a clear \"email already in use\" message. You may see 404 | This page could not be found. How to fix it - Confirm the email isn't already on another Paragon account. If the invited person ever signed up (including a trial) with that email, they need to free it before they can accept a new team invite. - Free the email on the old account. Log in to the original account -> Account Settings -> change the email to something like email address. - Have a team admin check Settings -> Team members for a pending invite to that email. If there's one, click Resend. If there isn't one, send a new invite to the same work email. - Accept the new invite in an incognito/private window. Use the latest email only. Don't reuse an old Join Now link. If it still fails Open the invite link, then: 1. Open Developer Tools (Chrome: right-click -> Inspect, or Cmd+Option+I / Ctrl+Shift+I) 2. Go to the Network tab 3. Check console logs 4. Find any request that is red or has status 4xx (often /teams/.../invite/... or /api/auth/accept-invite) 5. Share that with email address and one of our support team can investigate it further for you.`},{\"title\":`When is an integration enabled?`,\"description\":`Connecting an account is not the same as completing required User Settings. What each one unlocks.`,\"href\":`/knowledge-base/when-is-an-integration-enabled`,\"topic\":`authentication`,\"tags\":[],\"keywords\":[],\"body\":`It depends what you are asking. Connecting the account (signing in) is not the same as filling in required settings. Authentication for an integration is done as soon as the user connects their account in the Connect Portal. Workflows, including the Integration Enabled trigger, wait until every required User Setting is filled in. The Proxy API can send requests immediately after authentication. If you are seeing an integration marked as connected before a required field (for example a workspace or group ID) is set, that is expected. Workflows associated with those required fields fail as not enabled, but the user's authentication to the integration itself is complete. Details | What you want to do | When it is allowed | | --- | --- | | User has a valid connected account | After they complete sign-in / OAuth in the Connect Portal | | Run workflows, including Integration Enabled | After sign-in and all required User Settings are filled in | | Send a Proxy request | After sign-in. Required User Settings are for workflows, not Proxy | Required User Settings are the fields you mark required in Customize Connect Portal > Configuration. See User Settings. If a setting is not required, it does not gate workflows or Integration Enabled. The Integration Enabled trigger is documented this way already: it fires when the user has connected an account and completed any required User Settings. A successful Proxy call after Connect does not mean workflows are ready. A workflow that is not ready does not mean Proxy will fail. Related questions User Settings Integration Enabled Trigger Proxy API Headless Connect Portal (enabled on getUser)`},{\"title\":`Workflow runs twice from one App Event or integration trigger`,\"description\":`Multi-Configuration runs a workflow once per configuration that has it enabled. How to target a single one.`,\"href\":`/knowledge-base/duplicate-workflow-executions`,\"topic\":`workflows`,\"tags\":[],\"keywords\":[],\"body\":`If one App Event or integration event produces two (or N) workflow executions with identical payloads, this article explains why and how to stop it. The usual cause is Multi-Configuration: Paragon runs the workflow once for every configuration that has it enabled. Jump to Fix. Symptoms Task History shows two or more executions of the same workflow a few seconds apart. The payloads are identical, including any primary key or record id your app sent. Your application logs show a single POST to the App Event endpoint, or a single event from the integration. The Connected User has more than one configuration (or more than one connected account) for that integration, and the workflow is enabled on more than one of them. You will not see an error. Each extra run succeeds. Cause App Events are one-to-many. One event can start every matching workflow for that Connected User. With Multi-Configuration, \"matching\" includes every configuration on the credential that has the workflow enabled. So if a user has three configurations, and two of them have \"Create issue\" enabled, one App Event produces two executions. The payloads look the same because they are the same event, applied to two configurations. The same fan-out applies to Integration Triggers. An \"Issue Updated\" (or similar) event from the provider is delivered to every configuration that has that workflow enabled. You cannot attach targeting headers to an inbound provider webhook. This is expected platform behavior. It is not a retry and not a duplicate POST from your app. | Trigger | Untargeted behavior | Can you target one configuration on the request? | | --- | --- | --- | | App Event | Runs once per configuration (and credential) that has a matching workflow enabled | Yes. Send X-Paragon-Credential and X-Paragon-Configuration-Id (see How to send an App Event to a single configuration). | | Integration Trigger | Runs once per configuration that has the workflow enabled | No. Filter inside the workflow, or enable the workflow on only one configuration. | | Request Trigger | Addresses one workflow | Use X-Paragon-Credential / selectedCredentialId when the user has multiple accounts. | Fix App Events: target one configuration Send the event to a single configuration. Pass both headers on POST /projects/ /sdk/events/trigger: X-Paragon-Credential: the connected-account UUID X-Paragon-Configuration-Id: the configuration UUID, or ext: plus the external ID you assigned when creating the configuration A successful targeted event returns 201. Sending only X-Paragon-Configuration-Id can return 400 Bad Request. Full steps, including how to read both IDs from GET /sdk/me or paragon.getUser(), are in How to send an App Event to a single configuration. Integration Triggers: you cannot target the inbound event The provider calls Paragon. There is no place to set X-Paragon-Configuration-Id. Use one of the following, or combine them. Enable the workflow on only one configuration. Leave it disabled on the others. You can do this in the Connect Portal for that configuration, or with paragon.enableWorkflow / paragon.disableWorkflow and selectedConfigurationId (Multi-Configuration). Give each configuration a required User Setting that uniquely identifies it, then use that setting in the workflow so only the matching configuration proceeds. Examples: a project key, issue type, folder, or assignee. Add the setting under Customize Connect Portal > Configuration, mark it required, and reference it from a Conditional step or from the trigger's own filters (for example a JQL query that includes {{settings.project}}). See User Settings. Make the workflow idempotent. Before creating or updating a record in your app, check whether that provider record was already synced. Extra executions then become no-ops. Untargeted duplication is the default. Multi-Configuration is for cases where each configuration should run with its own settings (different folders, projects, or teams), or where you have already constrained the trigger so each configuration only matches its own events. Prevention When you send App Events, always include both targeting headers if the user can have more than one configuration. Do not enable the same unfiltered Integration Trigger workflow on every configuration unless you want one execution per configuration. Prefer User Settings that make each configuration's trigger unique (project, issue type, folder, channel) over relying on users to avoid \"duplicate\" configurations. If you also use Multi-Account Authorization, one event can run once per connected account as well. Pass X-Paragon-Credential (and a configuration ID when you use Multi-Configuration) instead of leaving the event untargeted. Still having issues? Send support: The workflow Task History IDs for the duplicate runs, and the time gap between them Whether the trigger is an App Event or an Integration Trigger How many configurations (and connected accounts) the user has, and which of them have the workflow enabled For App Events: the request URL, whether you sent X-Paragon-Credential and X-Paragon-Configuration-Id, and the exact header values (configuration UUID vs ext: + external ID) For 400 on the App Event endpoint: the response body, and whether the same request succeeds with those headers removed Related articles How to send an App Event to a single configuration Multi-Configuration App Events Integration Triggers User Settings Multi-Account Authorization`}]};const[query,setQuery]=useState(``);const haystackOf=item=>[item.title,item.description,...item.tags??[],...item.keywords??[],item.body??``,item.kind??``].join(` `).toLowerCase();const matchesWord=(item,word)=>{const haystack=haystackOf(item);if(haystack.includes(word)){return item.title.toLowerCase().includes(word)?5:2}if(word.length\u003c4){return 0}const tokens=haystack.split(/[^a-z0-9]+/);const fuzzy=tokens.some(token=>token.startsWith(word)||token.length>=4\u0026\u0026word.startsWith(token));return fuzzy?1:0};const searchItems=(items,words)=>{if(!words.length){return items}return items.map(item=>{let score=0;for(const word of words){const wordScore=matchesWord(item,word);if(!wordScore){return null}score+=wordScore}return{item,score}}).filter(Boolean).sort((a,b)=>b.score-a.score).map(entry=>entry.item)};const articles=(KB_SEARCH_INDEX.articles??[]).map(article=>({...article,kind:`Article`}));const guides=GUIDES.map(guide=>({...guide,kind:`Guide`}));const words=query.toLowerCase().trim().split(/\\s+/).filter(Boolean);const searching=words.length>0;const guideResults=searchItems(guides,words);const articleResults=searchItems(articles,words);const mixedResults=searchItems([...guides,...articles],words);const featured=GUIDES.filter(guide=>guide.featured);const tagsFor=item=>{const topicTitle=TOPIC_TITLES[item.topic];const rest=(item.tags??[]).filter(tag=>!topicTitle||!topicTitle.toLowerCase().includes(tag.toLowerCase()));return[item.kind,topicTitle,...rest].filter(Boolean)};const renderItem=item=>_jsx(`li`,{children:_jsxs(_components.a,{className:`kb-article`,href:item.href,...item.external?{target:`_blank`,rel:`noreferrer`}:{},children:[_jsx(`div`,{className:`kb-article-title`,children:item.title}),item.description?_jsx(`div`,{className:`kb-article-desc`,children:item.description}):null,_jsx(`div`,{className:`kb-tags`,children:tagsFor(item).map(tag=>_jsx(`span`,{className:`kb-tag`,children:tag},`${item.href}-${tag}`))})]})},item.href);return _jsxs(`div`,{className:`not-prose kb-home mint-px-4 mint-py-12 lg:mint-py-16 mint-max-w-5xl mint-mx-auto`,children:[_jsx(`style`,{children:`\n .kb-home {\n display: flex;\n flex-direction: column;\n gap: 2.5rem;\n }\n .kb-search {\n display: block;\n width: 100%;\n box-sizing: border-box;\n margin: 0;\n padding: 12px 16px;\n border-radius: 12px;\n border: 1px solid rgba(0, 0, 0, 0.15);\n background-color: #fff;\n font-size: 15px;\n font-family: inherit;\n color: #111;\n }\n .dark .kb-search {\n background-color: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.15);\n color: rgba(255, 255, 255, 0.95);\n }\n .kb-search:focus {\n outline: 2px solid #6645e6;\n outline-offset: 1px;\n }\n .kb-section {\n display: flex;\n flex-direction: column;\n gap: 0.9rem;\n }\n .kb-section-label {\n display: block;\n margin: 0;\n font-size: 0.75rem;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: rgba(55, 55, 58, 0.62);\n }\n .dark .kb-section-label {\n color: rgba(255, 255, 255, 0.55);\n }\n .kb-title {\n margin: 0;\n font-size: 2.5rem;\n font-weight: 500;\n letter-spacing: -0.03em;\n color: #18181b;\n }\n .dark .kb-title {\n color: rgba(255, 255, 255, 0.95);\n }\n .kb-lede {\n margin: 0.75rem 0 0;\n font-size: 1.125rem;\n color: rgba(55, 55, 58, 0.72);\n }\n .dark .kb-lede {\n color: rgba(255, 255, 255, 0.6);\n }\n .kb-featured-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n }\n .kb-topics-grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n }\n @media (max-width: 900px) {\n .kb-topics-grid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n }\n @media (max-width: 640px) {\n .kb-featured-grid,\n .kb-topics-grid {\n grid-template-columns: 1fr;\n }\n }\n .kb-featured {\n display: block;\n padding: 18px 16px;\n border-radius: 14px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n background: #fff;\n text-decoration: none !important;\n color: inherit;\n }\n .dark .kb-featured {\n background: rgba(255, 255, 255, 0.03);\n border-color: rgba(255, 255, 255, 0.12);\n }\n .kb-featured:hover {\n border-color: rgba(102, 69, 230, 0.45);\n }\n .kb-featured-title,\n .kb-article-title {\n display: block;\n margin: 0;\n font-size: 1.05rem;\n font-weight: 600;\n line-height: 1.35;\n color: #18181b;\n }\n .dark .kb-featured-title,\n .dark .kb-article-title {\n color: rgba(255, 255, 255, 0.92);\n }\n .kb-featured-desc,\n .kb-article-desc {\n display: block;\n margin: 6px 0 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: rgba(55, 55, 58, 0.72);\n }\n .dark .kb-featured-desc,\n .dark .kb-article-desc {\n color: rgba(255, 255, 255, 0.58);\n }\n .kb-article-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n padding: 0;\n list-style: none;\n }\n .kb-article {\n display: block;\n padding: 14px 12px;\n border-radius: 12px;\n text-decoration: none !important;\n color: inherit;\n }\n .kb-article:hover {\n background: #f4f4f5;\n }\n .dark .kb-article:hover {\n background: rgba(255, 255, 255, 0.06);\n }\n .kb-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 10px;\n }\n .kb-tag {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 999px;\n background: rgba(102, 69, 230, 0.08);\n color: #5b3cc4;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.01em;\n }\n .dark .kb-tag {\n background: rgba(167, 139, 250, 0.16);\n color: #c4b5fd;\n }\n .kb-empty {\n margin: 0.5rem 0 0;\n font-size: 0.9rem;\n color: rgba(55, 55, 58, 0.72);\n }\n .dark .kb-empty {\n color: rgba(255, 255, 255, 0.6);\n }\n `}),_jsxs(`div`,{children:[_jsx(`h1`,{className:`kb-title`,children:`Guides`}),_jsx(`p`,{className:`kb-lede`,children:`Tutorials and knowledge base articles for building native integrations with Paragon.`})]}),_jsx(`input`,{type:`search`,className:`kb-search`,placeholder:`Search guides and articles`,value:query,onChange:event=>setQuery(event.target.value),\"aria-label\":`Search guides and articles`}),searching?_jsxs(`div`,{className:`kb-section`,children:[_jsx(`div`,{className:`kb-section-label`,children:`Results`}),mixedResults.length===0?_jsx(`div`,{className:`kb-empty`,children:`No guides or articles match that search.`}):_jsx(`ul`,{className:`kb-article-list`,children:mixedResults.map(renderItem)})]}):_jsxs(_Fragment,{children:[_jsxs(`div`,{className:`kb-section`,children:[_jsx(`div`,{className:`kb-section-label`,children:`Topics`}),_jsx(`div`,{className:`kb-topics-grid`,children:TOPICS.map(topic=>_jsxs(_components.a,{className:`kb-featured`,href:topic.href,children:[_jsx(`div`,{className:`kb-featured-title`,children:TOPIC_TITLES[topic.id]}),_jsx(`div`,{className:`kb-featured-desc`,children:topic.description})]},topic.id))})]}),_jsxs(`div`,{className:`kb-section`,children:[_jsx(`div`,{className:`kb-section-label`,children:`Featured guides`}),_jsx(`div`,{className:`kb-featured-grid`,children:featured.map(guide=>_jsxs(_components.a,{className:`kb-featured`,href:guide.href,children:[_jsx(`div`,{className:`kb-featured-title`,children:guide.title}),_jsx(`div`,{className:`kb-featured-desc`,children:guide.description}),_jsx(`div`,{className:`kb-tags`,children:tagsFor({...guide,kind:`Guide`}).map(tag=>_jsx(`span`,{className:`kb-tag`,children:tag},`${guide.href}-${tag}`))})]},guide.href))})]}),_jsxs(`div`,{className:`kb-section`,children:[_jsx(`div`,{className:`kb-section-label`,children:`All guides`}),_jsx(`ul`,{className:`kb-article-list`,children:guideResults.map(renderItem)})]}),_jsxs(`div`,{className:`kb-section`,children:[_jsx(`div`,{className:`kb-section-label`,children:`All articles`}),articleResults.length===0?_jsx(`div`,{className:`kb-empty`,children:`No knowledge base articles yet.`}):_jsx(`ul`,{className:`kb-article-list`,children:articleResults.map(renderItem)})]})]})]})};function _createMdxContent(props){const{_MdxComponentBoundary}={..._provideComponents(),...props.components};if(!_MdxComponentBoundary)_missingMdxReference(`_MdxComponentBoundary`,true);return _jsx(_MdxComponentBoundary,{name:`GuidesHome`,children:_jsx(GuidesHome,{})})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{GuidesHome,default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Guides","description":"Tutorials and knowledge base articles for building native integrations with Paragon.","mode":"custom","pagination":false,"href":"/guides"}}},"mdxSourceWithNoJs":null,"noJsSplices":[[19,69,"jsx:_jsx}=arguments["],[149,67011,"null;function _creat"]]},"mdxExtracts":{"tableOfContents":[],"codeExamples":{}},"jsLinks":["/_mintlify/custom/paragon/b66e6962cf6c82d8/cookiebot-mintlify-consent.js"],"dynamicCss":".mint-mx-auto {\n margin-left: auto;\n margin-right: auto\n}\n.mint-max-w-5xl {\n max-width: 64rem\n}\n.mint-px-4 {\n padding-left: 1rem;\n padding-right: 1rem\n}\n.mint-py-12 {\n padding-top: 3rem;\n padding-bottom: 3rem\n}\n@media (min-width: 1024px) {\n .lg\\:mint-py-16 {\n padding-top: 4rem;\n padding-bottom: 4rem\n }\n}","subdomain":"paragon","siteName":"Paragon Documentation","title":"Guides - Paragon Documentation","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-29T22:33:01.224Z","originalFileLocation":"guides.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Tutorials and knowledge base articles for building native integrations with Paragon.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Paragon Documentation\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Guides - Paragon Documentation\">\u003cmeta data-mint-head property=\"og:description\" content=\"Tutorials and knowledge base articles for building native integrations with Paragon.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Paragon Documentation\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.useparagon.com/guides\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://paragon.mintlify.app/_mintlify/api/og/v2?division=Guides\u0026amp;title=Guides\u0026amp;description=Tutorials+and+knowledge+base+articles+for+building+native+integrations+with+Paragon.\u0026amp;theme=13788743c298c81ac0d08ae9\u0026amp;signature=e5ef4d60f15607b0872a1932ea57a3f9e841e1f5fd8dfeb0499051afdd51ce3b\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Guides - Paragon Documentation\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Tutorials and knowledge base articles for building native integrations with Paragon.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://paragon.mintlify.app/_mintlify/api/og/v2?division=Guides\u0026amp;title=Guides\u0026amp;description=Tutorials+and+knowledge+base+articles+for+building+native+integrations+with+Paragon.\u0026amp;theme=13788743c298c81ac0d08ae9\u0026amp;signature=e5ef4d60f15607b0872a1932ea57a3f9e841e1f5fd8dfeb0499051afdd51ce3b\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/paragon/jEl23Mfv0t0lim0x/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Paragon Documentation\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#6645e6\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.useparagon.com/guides\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/guides.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.useparagon.com/#organization\",\"name\":\"Paragon Documentation\",\"url\":\"https://docs.useparagon.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/paragon/VsFfcYgo5-Qm6khT/logo/light.svg?fit=max\u0026auto=format\u0026n=VsFfcYgo5-Qm6khT\u0026q=85\u0026s=348909bc26f6451e89f4010d965f6e3f\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.useparagon.com#website\",\"name\":\"Paragon Documentation\",\"url\":\"https://docs.useparagon.com\",\"publisher\":{\"@id\":\"https://docs.useparagon.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.useparagon.com/guides#webpage\",\"url\":\"https://docs.useparagon.com/guides\",\"name\":\"Guides\",\"description\":\"Tutorials and knowledge base articles for building native integrations with Paragon.\",\"dateModified\":\"2026-10-29T22:33:01.224Z\",\"isPartOf\":{\"@id\":\"https://docs.useparagon.com#website\"},\"breadcrumb\":{\"@id\":\"https://docs.useparagon.com/guides#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.useparagon.com/guides#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Paragon Documentation\",\"item\":\"https://docs.useparagon.com\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Guides\",\"item\":\"https://docs.useparagon.com/guides\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.useparagon.com/guides#article\",\"headline\":\"Guides\",\"name\":\"Guides\",\"description\":\"Tutorials and knowledge base articles for building native integrations with Paragon.\",\"url\":\"https://docs.useparagon.com/guides\",\"mainEntityOfPage\":{\"@id\":\"https://docs.useparagon.com/guides#webpage\"},\"dateModified\":\"2026-10-29T22:33:01.224Z\",\"publisher\":{\"@id\":\"https://docs.useparagon.com/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.useparagon.com#website\"}}]}\u003c/script>","siteName":"Paragon Documentation","nav":{"global":null,"tabs":[{"tab":"Platform","pages":[{"title":"Platform","href":"/overview"}]},{"tab":"Agent OS","hidden":true,"pages":[{"title":"Agent OS","href":"/agent-os/overview"}]},{"tab":"Managed Sync","icon":"https://mintlify.s3.us-west-1.amazonaws.com/paragon/assets/sync-icon-light.svg","pages":[{"title":"Managed Sync","href":"/managed-sync/overview"}]},{"tab":"ActionKit","icon":"https://mintlify.s3.us-west-1.amazonaws.com/paragon/assets/actions-icon-light.svg","pages":[{"title":"ActionKit","href":"/actionkit/overview"}]},{"tab":"Workflows","icon":"https://mintlify.s3.us-west-1.amazonaws.com/paragon/assets/workflow-icon-light.svg","pages":[{"title":"Workflows","href":"/workflows/overview"}]},{"tab":"Integrations","pages":[{"title":"Integrations","href":"/resources/integrations"}]},{"tab":"API Reference","pages":[{"title":"API Reference","href":"/apis/api-reference"}]},{"tab":"Guides","menu":[{"item":"Home","groups":[{"group":"Guides","root":{"title":"Guides","description":"Tutorials and knowledge base articles for building native integrations with Paragon.","mode":"custom","href":"/guides"},"pages":[{"title":"Guides","description":"Tutorials and knowledge base articles for building native integrations with Paragon.","mode":"custom","href":"/guides"}]}]},{"item":"Authentication","groups":[{"group":"Authentication","pages":[{"title":"Authentication","href":"/tutorials/building-an-in-app-integrations-catalog"}]}],"global":{"anchors":[{"anchor":"Back to all guides","icon":"arrow-left","href":"/guides"}]}},{"item":"Deploying Integrations","groups":[{"group":"Deploying Integrations","pages":[{"title":"Deploying Integrations","href":"/knowledge-base/git-sync-missing-commits"}]}],"global":{"anchors":[{"anchor":"Back to all guides","icon":"arrow-left","href":"/guides"}]}},{"item":"Custom Integrations or Webhooks","groups":[{"group":"Custom Integrations or Webhooks","pages":[{"title":"Custom Integrations or Webhooks","href":"/knowledge-base/api-base-url-ip-403"}]}],"global":{"anchors":[{"anchor":"Back to all guides","icon":"arrow-left","href":"/guides"}]}},{"item":"Billing and Account Management","groups":[{"group":"Billing and Account Management","pages":[{"title":"Billing and Account Management","href":"/knowledge-base/email-invite-404"}]}],"global":{"anchors":[{"anchor":"Back to all guides","icon":"arrow-left","href":"/guides"}]}},{"item":"ActionKit","groups":[{"group":"ActionKit","pages":[{"title":"ActionKit","href":"/actionkit/actionkit-for-tool-calling"}]}],"global":{"anchors":[{"anchor":"Back to all guides","icon":"arrow-left","href":"/guides"}]}},{"item":"Workflows","groups":[{"group":"Workflows","pages":[{"title":"Workflows","href":"/tutorials/building-a-salesforce-integration"}]}],"global":{"anchors":[{"anchor":"Back to all guides","icon":"arrow-left","href":"/guides"}]}}]},{"tab":"Release Notes","pages":[{"title":"Release Notes","href":"/changelog/product-updates"}]}]},"title":"Guides - Paragon Documentation","buildId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","deploymentId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","dynamics":[],"chunks":[]}