Skip to main content
RapidDev - Software Development Agency
flutterflow-integrationsFlutterFlow API Call

Basecamp

Connect FlutterFlow to Basecamp using the API Calls panel, but because Basecamp requires OAuth 2.0 with a redirect callback, you must run the authorization flow in a Firebase Cloud Function — not in the FlutterFlow client. The Cloud Function exchanges the auth code for a Bearer token and handles refresh. FlutterFlow then calls Basecamp's REST v4 API (account-scoped base URL) with that token and a required User-Agent header.

What you'll learn

  • How to register a Basecamp app at integrate.37signals.com and set up the OAuth credentials
  • How to write a Firebase Cloud Function that handles the OAuth authorization-code flow and token refresh
  • How to configure a FlutterFlow API Group to call Basecamp with a Bearer token and required User-Agent header
  • How to fetch Basecamp projects and to-do lists and bind the responses to ListViews
  • How to handle Basecamp's 50 req/sec rate limit to avoid 429 errors in a paginated list app
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced18 min read90 minutesProductivityLast updated July 2026RapidDev Engineering Team
TL;DR

Connect FlutterFlow to Basecamp using the API Calls panel, but because Basecamp requires OAuth 2.0 with a redirect callback, you must run the authorization flow in a Firebase Cloud Function — not in the FlutterFlow client. The Cloud Function exchanges the auth code for a Bearer token and handles refresh. FlutterFlow then calls Basecamp's REST v4 API (account-scoped base URL) with that token and a required User-Agent header.

Quick facts about this guide
FactValue
ToolBasecamp
CategoryProductivity
MethodFlutterFlow API Call
DifficultyAdvanced
Time required90 minutes
Last updatedJuly 2026

Basecamp in FlutterFlow: The OAuth 2.0 Challenge and How to Solve It

Basecamp is one of the most widely used team collaboration tools, and many founders want a mobile companion app for their team without sending everyone to a browser. Basecamp's pricing — roughly $15 per user per month, or a flat ~$299/month for Pro Unlimited — makes it a cost-effective choice for growing teams. Building a FlutterFlow mobile app on top of it gives team members a native iOS/Android experience for checking to-dos, reading campfire messages, and tracking project progress.

The integration challenge is authentication. Unlike simpler tools that accept a static API key in a header, Basecamp requires OAuth 2.0 using the authorization-code flow. This means: your app registers at integrate.37signals.com, receives a client ID and client secret, redirects the user to Basecamp to approve access, receives a one-time code at a redirect callback URL, and then exchanges that code for an access token plus a refresh token. The redirect callback URL must be a real HTTPS endpoint — which a FlutterFlow client app cannot be. Attempting to run this flow entirely client-side fails because the client secret must never be exposed, and there is no safe way to store it in a compiled Flutter app.

The solution used by developers building production Basecamp integrations on FlutterFlow is a Firebase Cloud Function acting as an OAuth broker. The Cloud Function owns the client secret, handles the code-to-token exchange, stores the access and refresh tokens securely, and refreshes them when they expire (typically after two weeks). FlutterFlow calls the Cloud Function to get a valid token, then calls Basecamp's REST v4 API with that token as a Bearer header. Every request also needs a User-Agent header with a contact email — 37signals blocks anonymous API calls. Basecamp's API rate limit is 50 requests per second per token; ListView implementations that fan out many parallel calls can hit this quickly, so pagination and modest polling intervals matter.

Integration method

FlutterFlow API Call

Basecamp uses OAuth 2.0 with an authorization-code flow that requires a redirect callback URL — something a FlutterFlow client app cannot safely host. The correct architecture is a Firebase Cloud Function that handles the OAuth handshake, stores and refreshes access tokens, and then serves as the token broker for your FlutterFlow app. FlutterFlow's API Calls panel then calls either the Cloud Function or Basecamp directly with the Bearer token, using an account-scoped base URL (https://3.basecampapi.com/ACCOUNT_ID/) and a mandatory User-Agent header on every request.

Prerequisites

  • A Basecamp account (any paid plan) and admin access to register an API application
  • A registered OAuth app at integrate.37signals.com — you will need the client ID and client secret
  • Your Basecamp Account ID — visible in your account URL (https://3.basecamp.com/ACCOUNT_ID)
  • A Firebase project with Cloud Functions enabled (Blaze pay-as-you-go plan required for Cloud Functions to make outbound HTTP calls)
  • A FlutterFlow project (any plan) with at least one screen; Supabase Edge Functions can substitute for Firebase Cloud Functions if you prefer

Step-by-step guide

1

Register a Basecamp app and get your OAuth credentials

Open your browser and navigate to https://integrate.37signals.com. Sign in with your Basecamp account credentials. Click 'Register an application' (or 'New App'). Fill in the form: - Application name: your app's name (e.g., 'Acme Team Dashboard') - Company/your name: your company name - Website URL: your company or product website - Redirect URI: this is the HTTPS URL of the Firebase Cloud Function you are about to create — for now, enter a placeholder like https://us-central1-YOUR-PROJECT.cloudfunctions.net/basecampOAuth and update it after deploying the function - Product description: briefly describe what the app does After submitting, 37signals will display your Client ID and Client Secret. Copy both to a secure location — your password manager, not a notes file. The client secret is shown only once in some flows, so do not navigate away before saving it. Also note your Basecamp Account ID. When you log into Basecamp, your URL is https://3.basecamp.com/ACCOUNT_ID — the number after the domain is your account ID. This appears in every API endpoint URL. If you manage multiple accounts, you need the ID for each account you want to query.

Pro tip: The redirect URI you set in the 37signals app dashboard must match exactly what your Cloud Function uses — including the path, case, and no trailing slash. A mismatch causes OAuth to fail with a 'redirect_uri_mismatch' error that is very difficult to debug.

Expected result: You have a client ID and client secret from integrate.37signals.com, your Basecamp Account ID noted, and a placeholder redirect URI set in the app registration.

2

Deploy a Firebase Cloud Function to handle OAuth and token refresh

The Firebase Cloud Function is the heart of this integration. It does three things: (1) handles the OAuth callback from Basecamp after the user approves access, (2) exchanges the authorization code for an access token and refresh token, and (3) provides a /token endpoint your FlutterFlow app can call to get a fresh access token at any time. In your Firebase project's console, go to Functions and click 'Get started' or 'Add function'. In the Firebase Functions code editor (or your local Firebase project if you develop locally), create the function shown in the code block below. Deploy it using the Firebase console deploy button or your usual deployment method. After deploying, go back to integrate.37signals.com and update the redirect URI in your app registration to the actual deployed function URL: https://us-central1-YOUR-PROJECT.cloudfunctions.net/basecampOAuth. To trigger the initial OAuth flow, your FlutterFlow app needs to open a browser to the Basecamp authorization URL. Use a Custom Action (Dart) with the url_launcher package to open: https://launchpad.37signals.com/authorization/new?type=web_server&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_FUNCTION_URL. The user approves access in their Basecamp browser session, gets redirected to your Cloud Function, and the function completes the exchange. From that point on, FlutterFlow calls your /token endpoint (or you cache the token in Firestore) to get a valid Bearer token.

index.js
1// Firebase Cloud Function — Basecamp OAuth broker (Node.js, index.js)
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const axios = require('axios');
5admin.initializeApp();
6const db = admin.firestore();
7
8const CLIENT_ID = functions.config().basecamp.client_id;
9const CLIENT_SECRET = functions.config().basecamp.client_secret;
10const REDIRECT_URI = functions.config().basecamp.redirect_uri;
11
12// Step 1: OAuth callback — exchange code for token
13exports.basecampOAuth = functions.https.onRequest(async (req, res) => {
14 const code = req.query.code;
15 if (!code) { res.status(400).send('Missing code'); return; }
16
17 try {
18 const tokenRes = await axios.post(
19 'https://launchpad.37signals.com/authorization/token',
20 null,
21 { params: { type: 'web_server', client_id: CLIENT_ID,
22 client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI, code } }
23 );
24 const { access_token, refresh_token, expires_in } = tokenRes.data;
25 await db.collection('basecampTokens').doc('default').set({
26 access_token, refresh_token,
27 expires_at: Date.now() + (expires_in * 1000)
28 });
29 res.send('Authorization complete. You can close this tab.');
30 } catch (e) {
31 res.status(500).send('Token exchange failed: ' + e.message);
32 }
33});
34
35// Step 2: Token endpoint called by FlutterFlow
36exports.basecampToken = functions.https.onRequest(async (req, res) => {
37 res.set('Access-Control-Allow-Origin', '*');
38 const doc = await db.collection('basecampTokens').doc('default').get();
39 const { access_token, refresh_token, expires_at } = doc.data();
40
41 if (Date.now() > expires_at - 60000) {
42 // Refresh the token
43 const r = await axios.post(
44 'https://launchpad.37signals.com/authorization/token',
45 null,
46 { params: { type: 'refresh', client_id: CLIENT_ID,
47 client_secret: CLIENT_SECRET, refresh_token } }
48 );
49 await db.collection('basecampTokens').doc('default').update({
50 access_token: r.data.access_token,
51 expires_at: Date.now() + (r.data.expires_in * 1000)
52 });
53 res.json({ token: r.data.access_token });
54 } else {
55 res.json({ token: access_token });
56 }
57});

Pro tip: Store the Cloud Function config (client ID, secret, redirect URI) using firebase functions:config:set basecamp.client_id='...' basecamp.client_secret='...' basecamp.redirect_uri='...' — never hardcode secrets inside the function source file.

Expected result: Two Cloud Functions are deployed: basecampOAuth (handles the OAuth callback) and basecampToken (returns a valid Bearer token to FlutterFlow). The redirect URI in your 37signals app registration matches the deployed basecampOAuth URL.

3

Create the FlutterFlow API Group pointing to Basecamp with Bearer token and User-Agent

Open your FlutterFlow project. In the left navigation, click API Calls → + Add → Create API Group. Name it 'Basecamp'. In the Base URL field, enter: https://3.basecampapi.com/YOUR_ACCOUNT_ID — replace YOUR_ACCOUNT_ID with the numeric ID from your Basecamp URL. This URL is account-scoped, not shared across all Basecamp accounts. Now add the headers in the Headers tab. You need two: 1. Authorization: Bearer {{access_token}} — this is a variable header, meaning the value comes from a FlutterFlow variable (explained next) 2. User-Agent: FlutterFlowApp/1.0 (youremail@company.com) — this is a static string; 37signals requires a User-Agent on every API request and rejects blank or generic ones In the Variables tab of the API Group (not the individual API Calls), add a group-level variable named access_token (String type). This variable will be passed in from your app's state every time you make a Basecamp API call. Your app fetches a valid token from the Firebase Cloud Function's /token endpoint and stores it in App State; then every API Call in this group picks it up automatically. To set up the token fetch: create a simple 'Initialize Basecamp' API Call inside the same group that calls https://us-central1-YOUR-PROJECT.cloudfunctions.net/basecampToken. On app launch (or before your first Basecamp call), trigger this call, parse the $.token JSON path from the response, and set it as an App State variable. All subsequent Basecamp API Group calls reference that App State variable for the Authorization header.

api_group_config.txt
1// API Group configuration reference:
2// Group name: Basecamp
3// Base URL: https://3.basecampapi.com/YOUR_ACCOUNT_ID
4// Variables: access_token (String)
5// Headers:
6// Authorization: Bearer {{access_token}}
7// User-Agent: FlutterFlowApp/1.0 (you@company.com)
8//
9// Token fetch call (separate API Group pointing to Firebase):
10// Base URL: https://us-central1-YOUR-PROJECT.cloudfunctions.net
11// Call: GET /basecampToken
12// JSON Path: $.token → App State variable 'basecampToken'

Pro tip: Add a second lightweight API Group pointing to your Firebase Cloud Function URL for the token fetch. Keep it separate from the main Basecamp group so FlutterFlow does not try to prepend the Basecamp base URL to the token endpoint.

Expected result: A 'Basecamp' API Group exists in the API Calls panel with the account-scoped base URL, an Authorization Bearer variable header, and a User-Agent static header. A second API Group or standalone call fetches the token from Firebase.

4

Add GET API Calls for Projects and To-do Lists, map JSON to Data Types

Inside the Basecamp API Group, click + Add API Call. Name it 'Get Projects'. Set the method to GET and the endpoint to projects.json (the full URL resolves to https://3.basecampapi.com/ACCOUNT_ID/projects.json). Basecamp appends .json to most endpoints — do not forget this, as the endpoint without it returns HTML instead of JSON. In the Response & Test tab, paste a sample Basecamp projects response. Basecamp returns an array of project objects with fields like id, name, description, status, and updated_at. Click Generate JSON Paths. Useful paths: $[*].id, $[*].name, $[*].description, $[*].status. Map these to a Data Type you create called 'BasecampProject' with fields: id (Integer), name (String), description (String), status (String). For to-do lists, add a second API Call: 'Get ToDo Lists'. The endpoint pattern is buckets/{{project_id}}/todosets/{{todoset_id}}/todolists.json — Basecamp's API nests resources under 'buckets' (synonymous with projects in the API). First call /projects.json and note the todoset id from each project's dock property (it's nested), or call the bucket directly. A simpler approach for beginners: call buckets/{{project_id}}/todolists.json and pass the project ID you selected. Create a Data Type 'BasecampTodoList' with fields: id (Integer), title (String), completed_ratio (String). Map the JSON paths accordingly. Basecamp includes a completed_ratio field showing completion percentage as a string like '3/10' — great for progress indicators. Don't forget to pass the access_token group variable when testing. In the test panel, FlutterFlow prompts for variable values — enter the current valid token from your Cloud Function to see live results.

json_paths.txt
1// JSON Path mappings after pasting sample response:
2// GET /projects.json:
3// $[*].id → BasecampProject.id (Integer)
4// $[*].name → BasecampProject.name (String)
5// $[*].description → BasecampProject.description (String)
6// $[*].status → BasecampProject.status (String)
7//
8// GET /buckets/{{project_id}}/todolists.json:
9// $[*].id → BasecampTodoList.id (Integer)
10// $[*].title → BasecampTodoList.title (String)
11// $[*].completed_ratio → BasecampTodoList.completed_ratio (String)

Pro tip: Basecamp's API returns a maximum of 15 items per page by default. If a project has more than 15 to-do lists, add a page query variable and check the response Link header for a 'next' URL to implement pagination.

Expected result: GET Projects and GET ToDo Lists API Calls are configured, tested, and show 200 OK with populated JSON. Data Types exist in FlutterFlow for projects and to-do lists.

5

Bind Basecamp data to ListViews and add to-do creation

On your main screen, add a ListView widget. Select it and open the Backend Query tab in the right panel. Set the query to API Call → Get Projects. FlutterFlow will call this on page load, passing the access_token from App State via the group variable. Inside the ListView, add a ListTile. Select the title Text widget → Set from Variable → 'Project Item → name'. Add the description below it similarly. For navigation: add an On Tap action to the ListTile → Navigate To → [new screen: ToDoListsScreen]. Create that screen with two parameters: project_id (Integer) and project_name (String). Pass the tapped project's id and name. On ToDoListsScreen, add another ListView backed by Get ToDo Lists, passing the project_id parameter as the API Call variable. Inside the to-do list's ListView items, you can show the completed_ratio string or parse it to display a LinearProgressBar. FlutterFlow's conditional visibility widgets let you show a green checkmark for lists with '0 remaining' status. To create a to-do via POST: add a POST API Call 'Create ToDo' with endpoint buckets/{{project_id}}/todolists/{{todolist_id}}/todos.json. Body: {"content": "{{title}}", "due_on": "{{due_date}}"}. On a form screen, wire a Button's action to this API Call, passing the relevant variables. The 201 response body contains the created todo object — map $.id to confirm creation. Keep the 50 req/sec limit in mind. If your ListView renders 20 items and each fires a sub-request for nested resources, you can hit the limit. Use paginated fetches and avoid firing API calls in a widget's build cycle — trigger them with explicit user actions or page-load queries instead.

create_todo_body.json
1// POST /buckets/{{project_id}}/todolists/{{todolist_id}}/todos.json
2// Method: POST
3// Headers: inherited from API Group (Bearer + User-Agent)
4// Body (JSON):
5{
6 "content": "{{title}}",
7 "due_on": "{{due_date}}",
8 "notify": false
9}

Pro tip: Basecamp's API uses .json suffixes on endpoints but returns the Link header for pagination instead of embedding page info in the response body. If you need to load all items, call the first page, read the next URL from the Link header, and chain calls — best done in a Custom Action.

Expected result: The app shows a list of Basecamp projects. Tapping a project navigates to a to-do lists screen. A create-task form posts new to-dos to Basecamp successfully, returning 201 Created.

6

Handle token refresh and rate-limit errors gracefully

Basecamp access tokens expire — typically after about two weeks. Your Firebase Cloud Function handles refresh automatically (the /token endpoint checks expiry and refreshes before serving the token), but your FlutterFlow app needs to handle the case where a 401 is returned mid-session if the cached token in App State is stale. Add error handling to every API Call action: in the Action Flow Editor, after the API Call node, add an If/Else conditional on the API response code. If the code is 401, add an action to call your token fetch API Call again, update the App State variable, and retry the original call once. This covers most 'session expired' scenarios. For 429 Too Many Requests (rate limit exceeded), add a Wait action (FlutterFlow's built-in Delay action) of 1-2 seconds before retrying. The Basecamp response includes RateLimit-Remaining and RateLimit-Reset headers — you cannot easily read response headers in FlutterFlow's standard API Call binding, but the Cloud Function proxy can read them and return the retry-after value in the JSON body if needed. Log errors to Firestore or a simple Snack Bar so users know when the app is rate-limited. A 'Refreshing data...' loading state during retries prevents users from thinking the app crashed. Test these flows in FlutterFlow's Test Mode by temporarily setting a wrong token to trigger a 401, then watching the retry logic execute. If you find this Cloud Function setup too complex for your timeline, RapidDev's team sets up FlutterFlow OAuth integrations like this every week — you can book a free scoping call at rapidevelopers.com/contact to discuss whether a pre-built solution fits your needs.

action_flow_pattern.txt
1// Action Flow pattern (describe in FlutterFlow Action Flow Editor):
2// 1. Call API: Get Projects (or any Basecamp call)
3// 2. Condition: API Response Code == 401
4// TRUE branch:
5// a. Call API: Fetch Token (Firebase /basecampToken)
6// b. Update App State: basecampToken = response.token
7// c. Call API: Get Projects (retry with fresh token)
8// FALSE branch:
9// a. Condition: API Response Code == 429
10// TRUE: Delay 2 seconds → retry once
11// FALSE: Bind response to ListView (success path)

Pro tip: Set the basecampToken App State variable to persist across sessions (FlutterFlow's 'Persist' option in App State) so the user does not have to re-authorize every time they open the app — as long as the token hasn't expired.

Expected result: The app gracefully handles expired tokens by fetching a fresh one from Firebase and retrying. Rate-limit errors (429) trigger a short delay and one retry. Users see loading indicators rather than blank screens or unhandled crashes.

Common use cases

Team to-do dashboard for remote teams

A fully remote company builds a FlutterFlow iOS app where team members log in with their Basecamp credentials (via the OAuth flow) and see all projects they have access to. They can browse to-do lists, check off completed items, and view recently added messages — all from a native mobile experience without opening a browser.

FlutterFlow Prompt

Build a FlutterFlow app that authenticates with Basecamp via OAuth, lists all the user's active projects, and lets them tap into any project to see its to-do lists with completion status.

Copy this prompt to try it in FlutterFlow

Client project status portal

An agency creates a branded FlutterFlow app for clients to track their project milestones. The app reads from a single shared Basecamp account (the agency's) using a long-lived token in the Firebase Cloud Function, filtering projects by client. Clients see clean progress views without needing their own Basecamp login.

FlutterFlow Prompt

Build a read-only project status screen that loads Basecamp to-do lists for a specific project ID and shows a completion percentage bar for each list.

Copy this prompt to try it in FlutterFlow

Internal ops app with task creation

An operations team at a logistics company builds a FlutterFlow Android tablet app for warehouse supervisors. The app shows today's Basecamp to-dos assigned to the shift and lets supervisors add new tasks directly from the tablet — all routed through the Firebase proxy to keep credentials safe.

FlutterFlow Prompt

Build a task management screen that reads Basecamp to-do lists for a specific project and lets the user create new to-do items from a form that submits via a POST API call.

Copy this prompt to try it in FlutterFlow

Troubleshooting

OAuth flow fails with 'redirect_uri_mismatch' error

Cause: The redirect URI used in the authorization URL does not exactly match the one registered in the Basecamp app at integrate.37signals.com. Even a trailing slash difference causes this error.

Solution: Go to integrate.37signals.com, open your app registration, and compare the registered redirect URI character by character with the URL your Cloud Function is deployed at. Update the registered URI to match exactly. Also confirm the Cloud Function is fully deployed and accessible via HTTPS before testing the OAuth flow.

Every API call returns 401 Unauthorized even with a fresh token

Cause: The Authorization header is missing 'Bearer ' (with a space) before the token value, or the access_token App State variable is empty or not being passed to the API Group variable.

Solution: Open the API Group in FlutterFlow's API Calls panel and check the Authorization header value field — it should read exactly: Bearer {{access_token}} with no extra characters or quotes. In the action that calls the API, confirm the access_token group variable is being set from App State. Add a debug Snack Bar action before the API call to display the current App State token value.

Basecamp API returns HTML instead of JSON

Cause: The endpoint is missing the .json suffix that Basecamp requires on most REST endpoints. Without it, Basecamp serves an HTML redirect or login page.

Solution: Append .json to every Basecamp API endpoint in your API Calls. For example, use projects.json instead of projects, and buckets/{{id}}/todolists.json instead of buckets/{{id}}/todolists. Verify in the Test tab — the response type should be application/json, not text/html.

429 Too Many Requests on the project list screen

Cause: The ListView is triggering individual API calls per list item (e.g., loading to-do counts for each project), quickly exceeding Basecamp's 50 requests/second cap for the access token.

Solution: Refactor the screen to load the project list in a single call and defer nested data (to-do lists, message counts) until the user taps a project. Avoid backend queries that run inside a ListView's item builder — use page-level queries instead and pass the full data to child items.

Best practices

  • Never store the Basecamp client secret in FlutterFlow App State or Dart code — keep it exclusively in Firebase Cloud Function environment config.
  • Always include User-Agent with a contact email on every Basecamp API request; 37signals requires it and will block requests that omit it without a clear error message.
  • Handle token expiry in your Action Flow with a 401 → refresh → retry pattern so users do not get silently logged out after two weeks.
  • Respect Basecamp's 50 req/sec rate limit by loading data page-level (one call per screen open) rather than per-list-item; use pagination for large project lists.
  • Store the access token in FlutterFlow's persisted App State so users do not have to re-authenticate on every app launch — just refresh if the token is expired.
  • Test OAuth flows on a real device or TestFlight build, not just FlutterFlow's Run Mode, because the url_launcher-based browser redirect behaves differently in a real app session.
  • Add loading state and error state widgets to every Basecamp-backed ListView so users see feedback during the OAuth token fetch and graceful error messages on 401/429 responses.
  • Scope your Basecamp app registration to read-only permissions if your app only displays data — this limits the blast radius if a token is ever leaked.

Alternatives

Frequently asked questions

Can I avoid the Firebase Cloud Function and do OAuth entirely in FlutterFlow?

No, not safely. Basecamp's OAuth authorization-code flow requires a server-side redirect callback to receive the authorization code, and the code-to-token exchange must use the client secret. Storing the client secret in a Flutter app means anyone who decompiles the binary can extract it. The Firebase Cloud Function (or Supabase Edge Function) is the required pattern for production Basecamp integrations in FlutterFlow.

What Basecamp plan do I need to use the API?

API access is available on all paid Basecamp plans. The standard plan is approximately $15/user/month, and Pro Unlimited is approximately $299/month flat for unlimited users. Verify current pricing at basecamp.com. There is no free plan with API access, though Basecamp occasionally offers 30-day trials.

How often do Basecamp access tokens expire and how does refresh work?

Basecamp access tokens typically expire after about two weeks. The Firebase Cloud Function in this guide handles refresh automatically — when the stored token is within 60 seconds of expiry, the /token endpoint exchanges the refresh token for a new access token and stores it. Refresh tokens themselves are long-lived but can be revoked by the user from their Basecamp account settings.

Does Basecamp have webhooks that can push data into my FlutterFlow app?

Basecamp 3 supports webhooks for events like to-do creation, message posting, and file uploads, but webhooks deliver data to your server — not directly into FlutterFlow. To use webhooks, you would add a second Firebase Cloud Function as the webhook receiver, which stores incoming events in Firestore, and your FlutterFlow app reads from Firestore for near-real-time updates.

Why does my API call return HTML instead of JSON?

Basecamp's REST API requires a .json suffix on most endpoints. Without it, the API serves an HTML redirect page instead of a JSON response. Append .json to every endpoint path — for example, use projects.json instead of projects, and buckets/1234/todolists.json instead of buckets/1234/todolists.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire FlutterFlow integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.