How to Build a Runnable Sample Repo for Headless Integrations
A step-by-step engineering blueprint for shipping a runnable sample repository that proves your headless integration API works and passes enterprise evaluations.
When a staff engineer evaluates your integration platform, they do not read your marketing pages. They look for a GitHub repository, clone it, and run npm install. If they do not see a working application on localhost:3000 connected to a real third-party API within five minutes, your platform fails the technical evaluation. Learning how to build a runnable sample repository for headless integrations step by step is the single highest-leverage activity your product team can execute to accelerate developer onboarding and close enterprise deals.
Providing abstract architecture diagrams, Swagger dumps, or isolated curl snippets is no longer sufficient. As detailed in our guide on publishing implementation-focused API code examples, evaluating engineers refuse to write boilerplate authentication logic, reverse-engineer undocumented OAuth state parameters, or guess how your API handles pagination just to see a proof of concept. They demand a copy-pasteable script that runs locally, authenticates against a real provider, returns real data, and proves your platform is worth a deeper look.
This guide breaks down exactly how to structure, code, and distribute a canonical sample repository that proves your headless API works. We will cover the metrics that govern developer adoption, the architectural differences you must demonstrate, and the exact code patterns required to handle the painful realities of rate limits and OAuth lifecycles in your examples.
Why Time to First Call (TTFC) Dictates Enterprise API Adoption
Time to First Call (TTFC) is the developer experience metric that measures the elapsed time from a developer signing up for your service to executing their first successful, authenticated API request that returns a non-error 2xx response. It is the clock that starts when a developer lands on your documentation and stops when their terminal logs show a 200 OK. It is widely considered the most important metric for a public API, and for good reason: nothing else in your funnel matters if the developer bounces before they see success.
Studies of developer adoption consistently show that developers abandon API integrations within the first hour if they cannot make a successful call in that time. If your onboarding process requires an engineer to spend three hours reading reference pages to understand your specific flavor of HMAC webhook signatures, they will simply recommend your competitor to their procurement team.
Providing runnable examples drastically alters this timeline. According to Postman's industry data, developers achieve their first successful API call 1.7 to 56 times faster when provided with a pre-built collection or an end-to-end developer tutorial with runnable API examples. That is not a rounding error. That is the difference between an engineering evaluation that finishes on a Tuesday afternoon and one that gets deprioritized indefinitely because the lead architect could not get past your OAuth callback.
The uncomfortable truth is that judgments are formed at this very early stage, likely while comparing your product among alternatives. If your documentation and onboarding process appears comparatively unorganized and riddled with errors, evaluating teams assume that is a reflection of your underlying technology. The evaluating engineer is not being unfair; they are triaging under a strict deadline.
A runnable sample repo attacks TTFC on every axis:
- Auth boilerplate is pre-written. No hand-rolling OAuth state parameters.
- Environment variables are documented. No spelunking through Postman collections for the right base URL.
- The happy path is guaranteed to work. If
mainis green in CI,npm install && npm run devis green on the reviewer's laptop. - The failure modes are visible. Rate limits, token expiry, and webhook signature checks are demonstrated in code, not buried in a footnote.
If you want more context on how to structure the tutorial content that ships alongside the repository, our step-by-step developer tutorial guide covers the writing and documentation side in detail.
The Architectural Fork: Headless APIs vs. Embedded iFrames
When engineering teams are tasked with building native integrations, they face an immediate architectural fork in the road. Every integration SDK ships in one of two shapes: an embedded iFrame (your vendor hosts the connection UI, and you drop in a script tag) or a headless API (you build the UI, and the vendor handles the OAuth backend and data normalization).
iFrames get you to a working demo in an afternoon. Headless APIs get you through enterprise procurement.
Here is the trade-off honestly:
| Concern | iFrame Approach | Headless API Approach |
|---|---|---|
| Time to working demo | Hours | Days |
| UI customization | Restricted to vendor CSS variables | Total control over native components |
| Security review | "Why is there a third-party iframe on our settings page?" | No cross-origin surface; standard fetch calls |
| CSP / sandbox rules | Constant negotiation and exceptions | Standard strict policies apply |
| SSO + custom auth flows | Limited or hacky workarounds | Native and seamless |
| Native mobile support | Painful (requires fragile webviews) | Native API implementation |
For enterprise buyers, the iFrame usually loses the strict security review. A vendor script running in the same DOM as your customer's admin console is a real question during a SOC 2 audit, and the answer is not always "it's fine."
However, headless architectures are inherently harder to evaluate because the developer has to build the UI themselves to test the flow.
The Golden Rule of Sample Repositories: If you ship an integration platform, your sample repository must demonstrate both a headless API implementation and an embedded iFrame implementation side by side. One repository. Two branches (or workspaces). Same backend proxy. This allows the evaluating engineer to physically see the trade-offs.
flowchart TD
subgraph iFrameDemo ["Embedded iFrame Approach"]
A["Your Frontend<br>(React/Vue)"] -->|"Loads script"| B["Vendor UI Widget"]
B -->|"Opaque auth flow"| C["Vendor Backend"]
end
subgraph HeadlessDemo ["Headless API Approach"]
D["Your Frontend<br>(Native Components)"] -->|"API requests"| E["Your Backend<br>(Node/Go)"]
E -->|"Server-to-server proxy"| F["Integration Platform"]
endFor a deeper dive into the specific security and state management differences between these approaches, refer to our guide on headless vs iframe architectures and our companion piece on publishing a runnable sample repo for both patterns.
Step 1: Structuring the Sample Repository for npm install
The physical structure of your repository dictates how quickly an engineer can understand your platform. The goal is to get the user from git clone to a running application with zero guesswork. If the README.md starts with a wall of prose, you have already lost thirty seconds.
Do not force developers to globally install obscure build tools, configure complex local databases, or navigate massive monorepo tooling just to run your sample. Use a standard structure with a shared backend and distinct frontend routes.
Here is the ideal directory structure for a Node.js/TypeScript environment:
truto-sample-integrations/
├── README.md # Five-minute quickstart, verbatim
├── .env.example # Every required var, with inline comments
├── package.json # One `dev` script to boot everything concurrently
├── turbo.json # Optional: lightweight monorepo orchestration
├── apps/
│ ├── web/ # React/Next.js frontend
│ │ ├── app/
│ │ │ ├── connect/ # Custom connection UI (headless)
│ │ │ └── connect-iframe/# Vendor iFrame drop-in
│ └── api/ # Express/Fastify backend proxy
│ ├── routes/
│ │ ├── oauth.ts # OAuth callback handler
│ │ ├── webhooks.ts # Signature verification
│ │ └── proxy.ts # Provider-agnostic API proxy
│ └── lib/
│ └── retry.ts # 429 exponential backoff logic
└── reference/
├── salesforce.md # Provider-specific gotchas
├── hubspot.md
└── stripe.mdThe README.md should be exactly five sections, in this strict order:
- Prerequisites (Node version, one line)
- Setup (three terminal commands, exactly)
- What you should see (a screenshot of the connection page and the resulting API response)
- How to switch to the iFrame branch/route (one command or click)
- How to test rate limit and webhook handling (two curl commands)
The .env.example file
The .env.example file is where most sample repositories fail. Do not ship a file with API_KEY=your-key-here. You must clearly document exactly where the developer finds each required key in your dashboard using inline comments.
# .env.example
# Your Truto API token. Get one at https://app.truto.one/settings/api
TRUTO_API_TOKEN=
# The tenant-scoped ID for the end-user connecting an integration.
# In production, this is your internal user or workspace ID.
# Find this in Truto Dashboard -> Settings -> General
TRUTO_INTEGRATED_ACCOUNT_ID=demo-user-001
# Optional: pin to a specific provider for the demo.
# Leave blank to show the full connector picker.
TRUTO_INTEGRATION_NAME=salesforce
# Port for the local backend server
PORT=8080Instruct the user to copy this file to .env, paste their keys, and run npm run dev. The package.json should have a single dev script that boots the backend and the frontend concurrently. If your setup requires more than those three steps, you have introduced too much friction. A developer who has never heard of your product should be able to fill in this file from your dashboard in under two minutes.
Step 2: Handling Authentication and OAuth State
The primary reason developers evaluate unified APIs is to avoid building OAuth state machines. OAuth is where 90% of integration sample repositories fall apart. The vendor's documentation shows a complex three-legged handshake, but the sample code hard-codes an access token generated from Postman. The developer runs the demo, sees a 200 OK, and then discovers an hour later that the token expired and nothing in the codebase refreshes it.
A credible sample repository must explicitly demonstrate the full lifecycle: initial authorization, token exchange, refresh, and revocation. And it must do it without forcing the developer to write a token management layer just to see the demo work.
Abstracting Token Lifecycles
One of the most painful realities of software engineering is managing OAuth refresh tokens. Providers have varying expiration times, different refresh limits, and undocumented edge cases when tokens are revoked or scopes change.
This is where a unified API materially reduces the amount of code you have to ship. Your sample repository should highlight that the developer does not need to write cron jobs or background workers to refresh tokens. Truto refreshes OAuth tokens shortly before they expire and passes the caller a stable connection ID that abstracts away the underlying token lifecycle.
In a headless implementation, the developer's backend must generate a magic link token, pass it to the frontend, and handle the redirect callback to persist the connection ID. The sample code should isolate this logic into an easily readable controller:
// apps/api/routes/oauth.ts
import express from 'express';
import { TrutoClient } from '@truto/node';
const router = express.Router();
const truto = new TrutoClient({ apiKey: process.env.TRUTO_API_TOKEN! });
// Step 1: Generate a session token/magic link for the headless UI
router.post('/api/connect/start', async (req, res) => {
try {
const link = await truto.magicLinks.create({
integrated_account_id: req.body.integratedAccountId,
integration_name: req.body.integrationName,
redirect_uri: `${process.env.APP_URL}/connect/complete`
});
res.json({ url: link.url });
} catch (error) {
res.status(500).json({ error: 'Failed to initialize OAuth flow' });
}
});
// Step 2: After redirect, the connection ID is what you persist.
// Notice there is no access_token, refresh_token, or expires_at in this DB call.
router.get('/api/connect/complete', async (req, res) => {
const { integrated_account_id } = req.query;
await db.connections.upsert({
user_id: req.session.userId,
connection_id: integrated_account_id as string,
});
res.redirect('/dashboard');
});That is the entire OAuth surface in the sample repository. Everything else - the code parameter exchange, PKCE, refresh token rotation, and the four different ways HubSpot's OAuth differs from Salesforce's - is handled behind the connection ID.
Do not skip this section in the sample repository just because the unified API handles it smoothly. The evaluating engineer needs to see that they do not have to write it. If the OAuth handler is missing from the code, they will assume you punted on it and go build a spike against the raw provider API instead.
Step 3: Demonstrating Real-World Constraints (Rate Limits & Webhooks)
A sample repository that only shows happy-path 200 OK responses is a toy. Senior engineers want to see how the system behaves under pressure. They assume the product will fall over at scale unless you explicitly show them the edge cases. You must demonstrate how to handle HTTP 429 Too Many Requests errors and incoming webhooks.
Handling Upstream Rate Limits
Unified APIs do not magically make upstream rate limits disappear. If a user attempts to pull 100,000 records from a CRM that allows 10 requests per second, the upstream API will reject the request.
Be explicit about who owns retry logic. Truto does not silently retry, throttle, or absorb HTTP 429 responses. When an upstream API rate-limits a request, Truto passes that 429 error straight through to your application. What it does do is normalize the chaotic upstream rate limit metadata into standardized headers (ratelimit-limit, ratelimit-remaining, ratelimit-reset) per the IETF specification, regardless of whether the underlying provider uses X-RateLimit-Reset, Retry-After, or a custom header format.
That contract is honest and puts the retry policy in your application code where it belongs. Your sample repository must contain a production-grade utility function demonstrating how a caller should implement exponential backoff using these normalized headers:
// apps/api/lib/retry.ts
/**
* Executes an API call and automatically retries on 429 errors
* using Truto's normalized IETF ratelimit headers.
*/
export async function callWithBackoff<T>(
fn: () => Promise<Response>,
maxAttempts = 5
): Promise<T> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fn();
if (res.ok) return res.json() as Promise<T>;
if (res.status === 429) {
// Truto normalizes upstream limits into standard headers
const resetTimeStr = res.headers.get('ratelimit-reset');
if (resetTimeStr) {
const resetSeconds = parseInt(resetTimeStr, 10);
const jitter = Math.random() * 500;
const delayMs = (resetSeconds * 1000) + jitter;
console.warn(`Rate limited. Retrying in ${resetSeconds} seconds...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
continue;
} else {
// Fallback exponential backoff if header is somehow missing
const fallbackDelay = Math.pow(2, attempt) * 1000 + (Math.random() * 500);
await new Promise(resolve => setTimeout(resolve, fallbackDelay));
continue;
}
}
throw new Error(`Provider error ${res.status}: ${await res.text()}`);
}
throw new Error('Max retries exceeded after HTTP 429');
}Add a companion script - scripts/hammer.ts - that fires 100 requests in parallel to prove the backoff behaves correctly against a real provider sandbox. By including this exact code, you prove to the evaluating engineer that your platform is designed for enterprise workloads, not just local prototypes. For a deeper look at this architecture, see our breakdown on handling API rate limits and webhooks.
Simulating Webhooks Locally
Webhooks are notoriously difficult to test locally. The other constraint to demonstrate is inbound webhooks. Every provider signs them differently. A unified webhook layer collapses that into one signature scheme your sample repository can verify in five lines.
Your sample repository should include instructions and a built-in script for tunneling local ports (using tools like ngrok or Cloudflare Tunnels) so developers can receive unified webhooks directly into their local backend. Include a dedicated route in your sample backend that verifies webhook signatures and processes incoming events:
// apps/api/routes/webhooks.ts
import express from 'express';
import { verifyTrutoSignature } from '@truto/node';
const router = express.Router();
router.post('/webhooks/truto', express.raw({ type: '*/*' }), (req, res) => {
const signature = req.header('x-truto-signature');
if (!verifyTrutoSignature(req.body, signature, process.env.TRUTO_WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString());
// Use event.id for idempotency - webhooks may be redelivered.
console.log(`Received verified event: ${event.type}`);
handleEvent(event);
res.status(200).send('ok');
});Showing a developer how to handle a unified contact.created event across five different CRMs in a single webhook handler is a massive "aha" moment.
Here is the end-to-end sequence the sample repository should visualize in its README to tie these concepts together:
sequenceDiagram participant Dev as Developer Laptop participant App as Sample App Backend participant Truto as Unified API participant Upstream as "Upstream (Salesforce)" Dev->>App: npm run dev Dev->>App: Click "Connect Salesforce" App->>Truto: Create magic link Truto-->>App: link.url App-->>Dev: Redirect to authorize Dev->>Upstream: OAuth authorize Upstream-->>Truto: Auth code Truto->>Upstream: Exchange for tokens Truto-->>App: connection_id App->>Truto: GET /crm/contacts Truto->>Upstream: GET /services/data/vXX.X/query Upstream-->>Truto: 429 + Retry-After Truto-->>App: 429 + ratelimit-reset header App->>App: Backoff (see retry.ts) App->>Truto: Retry GET /crm/contacts Truto-->>App: 200 OK
Step 4: Providing Curated Reference Integrations
Developers do not evaluate integration platforms in a vacuum. They evaluate them against specific, notoriously difficult APIs they have struggled with in the past. A sample repository that only shows a generic mock API, or only shows a basic Salesforce connection, is a sample repository that has not been evaluated by a real buyer. The buyer's next question is always "what does this look like for my specific provider?" - and if the answer requires a sales call, you have lost momentum.
The fix is a curated reference set: a small, deliberately chosen list of exemplar providers that each demonstrate a distinct configuration pattern. Not all 400+ connectors in your catalog. Ten to fifty, hand-picked because each one covers a different auth scheme, pagination style, or webhook signature format.
A reasonable exemplar set looks like this:
| Provider | Why it is in the reference set |
|---|---|
| Salesforce | OAuth2 + cursor pagination + rate limit headers + complex webhooks |
| HubSpot | OAuth2 + cursor + webhook verification handshake |
| Stripe | API key + bearer + massive cursor pagination |
| Airtable | OAuth2 + bearer + clean baseline for documentation |
| GitHub | OAuth2 + fine-grained tokens + link-header pagination |
| Shopify | OAuth2 + shop-scoped URLs + GraphQL |
| Zendesk | OAuth2 + offset pagination + custom signature formats |
Inside Truto, this pattern powers the CLI's build agent - a curated list of exemplar slugs that the agent pattern-matches against when scaffolding a new integration. The idea translates directly to a sample repository. Ship a /reference/ folder with one markdown file per exemplar provider, mimicking this structure:
apps/api/reference/
├── salesforce.md # Demonstrates complex OAuth + rate limits + error expressions
├── hubspot.md # Demonstrates webhook verification signatures
└── stripe.md # Demonstrates API key auth + massive cursor paginationWhen the evaluating engineer opens /reference/hubspot.md, they should see a working call, the exact response shape, the pagination style, the webhook signature format, one curl example that returns 200 OK, and one curl example that intentionally triggers 429.
When a developer runs the sample app, provide a dropdown in the UI that lets them toggle between these reference integrations. If they select Salesforce, the UI should execute a query that triggers pagination, proving that your unified API handles Salesforce's specific cursor implementation. Their next thought is "how much of my custom logic can I delete?" That is the conversion moment.
Caching Tip: Hash your reference list and version the cache. If your CLI or SDK fetches provider configs at runtime, invalidate the cache by hashing the sorted slug list rather than using a time-to-live (TTL). The list changes infrequently, and a hash-based version means the cache is stable across machines without a stale-data risk.
Step 5: Shipping the Repo (A Pre-Publish Checklist)
Before you push the repository public, run through this list. It is the difference between a sample repository that converts and one that sits abandoned with zero engagement on GitHub:
-
git clone && npm install && npm run devproduces a working localhost demo in under 5 minutes on a fresh laptop. -
.env.exampledocuments every variable with a link to exactly where the value comes from in the dashboard. - CI runs the full happy-path integration test against a real sandbox on every PR to prevent code rot.
- Both headless and iFrame branches build green and share the same backend proxy.
- The rate limit demo actually triggers an HTTP 429 and recovers automatically via backoff.
- The webhook demo verifies a signature and rejects a tampered payload.
- The README screenshot matches what the application actually renders.
- At least three exemplar providers have a
/reference/*.mdfile with working curl commands.
Where to Go From Here
Building a runnable sample repository is an engineering project, not a marketing task. It is a sales asset that happens to be written in TypeScript. Ship it with the same rigor you would ship a production feature: CI, code review, versioning, dedicated maintenance, and a changelog.
Stop relying on static documentation to sell highly technical infrastructure. Fork your documentation strategy, build a canonical sample repository with side-by-side headless and iFrame implementations, and watch your Time to First Call plummet. Every merged PR to the repository compounds your TTFC advantage against competitors who are still writing generic tutorial blog posts.
If you want to compress the timeline to publishing this repository, the fastest path is to skip building the OAuth, refresh, rate-limit, and webhook infrastructure yourself. Use a unified API as the backend of the sample, so the code you commit is the code your customers will actually write.
FAQ
- What is Time to First Call (TTFC) and why does it matter for headless integrations?
- TTFC is the elapsed time from a developer signing up for your API to their first successful 2xx response. Postman research shows a ready-to-run collection can make developers 1.7 to 56 times faster on that first call. It directly correlates with API activation and retention rates, making it the most critical developer experience metric to optimize.
- Should a sample repository demonstrate both headless and iFrame integrations?
- Yes. Ship one repository with two branches sharing the same backend proxy. iFrames are fast to demo but usually fail enterprise security and SOC 2 reviews, while headless APIs win on native UX and procurement. Showing both lets evaluating engineers see the trade-offs firsthand.
- How should a sample repository handle OAuth token refresh?
- If you are using a unified API, the sample code should not handle refresh logic. Truto refreshes OAuth tokens shortly before they expire and gives your application a stable connection ID. The sample code only persists that ID rather than managing access tokens, refresh tokens, and expiry timestamps, dramatically shrinking the required OAuth code.
- How should the sample repository handle API rate limits and 429 errors?
- The sample must include explicit retry and exponential backoff logic because unified platforms like Truto pass 429 errors directly to the caller rather than absorbing them. Truto normalizes upstream rate limit info into IETF-standard headers (ratelimit-reset), allowing the backoff code to read a single header format regardless of the upstream provider.
- How many providers should the sample repository demonstrate?
- Between 5 and 15 curated exemplars, each chosen because it covers a distinct authentication or pagination pattern (e.g., Salesforce for OAuth2 plus cursor, Stripe for API key plus bearer, HubSpot for webhook verification). This prevents duplicating coverage while proving the platform handles diverse API constraints.