API Integration Explained: How Apps Talk

Your CRM has 4,200 leads. Your email tool has a different segment list. Your billing system doesn't know either exists. Every Monday someone exports CSVs, runs a VLOOKUP, and imports them back — and every Monday something breaks. This is the pain APIs solve, if you understand how they actually work.
Most explainers stop at "API is like a waiter." That doesn't help you decide whether to use REST or webhooks, when polling is fine, or why your integration silently drops records at 3 AM. This post goes deeper: authentication types you'll actually encounter, the three integration patterns that cover 90% of business cases, and the failure modes nobody warns you about until you hit them in production.
What an API actually is (in engineer terms)
An API (Application Programming Interface) is a contract. One app publishes a set of endpoints — URLs that accept specific inputs and return specific outputs. Another app calls those endpoints to read or change data. That's it. No magic.
Almost every modern business app you use exposes an HTTP API. When you send a GET request to https://api.stripe.com/v1/customers/cus_123, Stripe's server looks up that customer and sends back JSON:
{
"id": "cus_123",
"email": "sam@acme.com",
"created": 1719878400,
"subscriptions": { "data": [...] }
}
The four common HTTP verbs map to four things you can do:
| Verb | Purpose | Example |
|---|---|---|
| GET | Read data | List all invoices |
| POST | Create a new record | Add a customer |
| PUT / PATCH | Update an existing record | Change email address |
| DELETE | Remove a record | Cancel a subscription |
Two things make an API usable in a business context: the docs are clear and the responses are predictable. If either is missing, you'll spend more time reverse-engineering than integrating. Before committing to a tool for a workflow you plan to automate, open its API docs. If they're thin, that's a warning.
REST is the dominant style — resources at URLs, JSON responses, standard HTTP status codes. You'll occasionally hit GraphQL (one endpoint, you specify what fields you want) and, in older enterprise systems, SOAP or XML-RPC. For SMB integrations you'll mostly see REST with JSON.
Authentication: how apps prove who they are
APIs need to know it's really you calling — otherwise anyone could pull your customer list. There are four auth patterns you'll meet, in order from simplest to most involved.
API keys. A long random string you paste into a header. Simple, but if it leaks, whoever has it is you. Rotate them, never commit them to Git, and scope them narrowly if the provider allows.
curl https://api.example.com/v1/orders \
-H "Authorization: Bearer sk_live_a8Zk...9x2Q"
OAuth 2.0. The "sign in with Google" flow. Your app redirects the user to the provider, they approve specific permissions ("read your calendar"), and you get back a token you use for API calls. Access tokens expire (usually 1 hour), so you also get a refresh token to mint new ones. This is what you'll use for Google Workspace, HubSpot, Salesforce, and most SaaS integrations that touch user data.
HMAC signed requests. You hash the request body with a shared secret. Stripe uses this for webhooks. It proves the message came from Stripe and wasn't tampered with in transit.
JWTs (JSON Web Tokens). A signed token containing claims (who you are, what you can do, when it expires). Common for machine-to-machine auth and internal microservices.
The practical rule: store secrets in environment variables or a secret manager (AWS Secrets Manager, Doppler, 1Password), never in code. If you're using a no-code tool, use its built-in credential store — those are usually encrypted at rest. And when a key gets shared over Slack "just for testing," rotate it. Every time.
The three integration patterns that cover 90% of cases
Most business integrations reduce to one of three patterns. Pick the wrong one and you'll fight it forever.
1. Polling. Your job runs on a schedule, hits an API, asks "anything new since last time?", and processes what comes back. Simple, resilient, and works with any API. The downsides: latency (data is stale between runs) and cost (you make API calls even when nothing changed).
# Runs every 15 minutes
last_check = get_last_run_timestamp()
new_orders = shopify.orders.list(updated_at_min=last_check)
for order in new_orders:
sync_to_accounting(order)
set_last_run_timestamp(now())
Use polling when the source doesn't support webhooks, when 5-15 minute latency is fine, and when volume is low.
2. Webhooks (event push). The source app calls your URL when something happens. New Stripe payment? Stripe POSTs the event to your endpoint within seconds. No polling, near-real-time, cheaper.
The catch: you now need a public URL that's always up, you need to respond fast (most providers time out at 5-10 seconds), and you need to handle retries and duplicates. Webhooks can arrive twice, out of order, or briefly not at all if the sender's queue is backed up.
3. Direct API calls in-flow. User clicks "create invoice" in your app, your backend calls the accounting API synchronously, waits for the response, shows the result. This is fine for user-initiated actions but a bad idea for background sync — one timeout and the user sees an error for a job that should have retried quietly.
| Pattern | Latency | Complexity | Best for |
|---|---|---|---|
| Polling | Minutes | Low | Batch sync, no webhook support |
| Webhooks | Seconds | Medium | Real-time triggers, notifications |
| Direct API | Immediate | Low | User-initiated actions |
Most production systems mix all three. Webhooks trigger the workflow, direct API calls fetch related data, and a nightly poll reconciles anything the webhooks missed.
Webhooks in practice: the gotchas nobody documents
Webhooks look easy in a tutorial. In production they'll bite you in specific, predictable ways.
Verify the signature. Anyone who guesses your webhook URL can POST to it. Every reputable provider signs their requests. Verify the signature before doing anything with the payload:
import hmac, hashlib
def verify_stripe_webhook(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Respond fast, process async. Return a 200 immediately, then do the work in a background job. If you try to send emails, update three databases, and call two APIs inside the webhook handler, you'll hit timeouts. When you time out, the provider retries — and you get duplicate processing.
Idempotency. Every event has an ID. Store the ones you've processed. If the same event arrives twice, skip it. This is non-negotiable for anything touching money or inventory.
if event_already_processed(event["id"]):
return 200
mark_processing(event["id"])
handle_event(event)
mark_complete(event["id"])
Have a fallback. Webhooks fail. Providers have outages, your endpoint goes down during a deploy, DNS hiccups. Run a nightly reconciliation poll that catches anything webhooks missed. Compare source and destination counts. Alert on drift.
A real example: Shopify → QuickBooks
Let's ground this in a concrete workflow an SMB actually runs: when a Shopify order is paid, create a matching invoice in QuickBooks Online, then post the payment.
The naive version: webhook fires on order paid → your code calls QuickBooks API → done. It'll work 95% of the time, which is why you'll spend all your time debugging the other 5%.
Here's what a durable version looks like:
- Shopify sends
orders/paidwebhook to your endpoint. - Endpoint verifies the HMAC signature using the shared secret.
- Endpoint checks if this order ID has been processed. If yes, return 200 and stop.
- Endpoint pushes the order into a queue (SQS, Redis, database table — whatever), returns 200.
- Worker picks up the job. Looks up (or creates) the QuickBooks customer by email.
- Worker maps Shopify line items to QuickBooks items. If an item doesn't exist, create it or route to a manual queue.
- Worker creates the invoice, then records the payment against it.
- On any 4xx from QuickBooks: log with full context, alert, do not retry blindly.
- On any 5xx or network error: retry with exponential backoff, up to 5 attempts.
- Nightly job pulls yesterday's Shopify orders and checks each has a matching QB invoice.
Notice how much of this isn't "call the API." The API call is 5 lines. The rest is the plumbing that keeps the integration honest when reality intrudes — refunds, partial payments, tax lines that don't map cleanly, an item name changed in one system but not the other.
Rate limits, retries, and error handling
Every API has limits. Hit them and you get a 429 Too Many Requests. The common patterns:
- Fixed window: 100 requests per minute, resets on the minute.
- Rolling window: 100 requests in any 60-second period.
- Token bucket: 100 tokens, refills at 10/second, each call costs 1.
- Concurrency limits: max N in-flight requests regardless of rate.
Read the docs. Most APIs return the current limit and remaining count in response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1719879000
Handle 429s with exponential backoff and jitter. Retry immediately and you'll make it worse:
import time, random
def call_with_retry(fn, max_attempts=5):
for attempt in range(max_attempts):
try:
return fn()
except RateLimitError as e:
wait = min(2 ** attempt + random.random(), 30)
time.sleep(wait)
raise Exception("Max retries exceeded")
Status codes matter. Roughly:
- 2xx: it worked. 201 usually means created, 204 means done with no content.
- 4xx: you sent something wrong. 400 = bad request, 401 = auth failed, 403 = auth is valid but you're not allowed, 404 = not found, 422 = validated but rejected (e.g. email format bad), 429 = rate limited.
- 5xx: they broke. Retry with backoff.
Never retry a 4xx (except 429 and sometimes 408). It'll fail the same way every time. Log it and move on or alert a human.
When to build vs. when to buy the integration
You have three real options for connecting business apps.
Write custom code. Full control, but you're on the hook for auth flows, retries, secret rotation, monitoring, dead-letter queues, and whatever the vendor changes next quarter. Worth it for core product features that are true differentiators.
Use an integration platform (iPaaS). Zapier, Make, n8n, Workato. You wire nodes together in a UI. Fast to build, decent for simple flows, expensive at scale, and painful once your logic gets past "if X then Y." Debugging inside a visual builder is worse than debugging code.
Managed integration built for your stack. Someone builds and runs the integration for you. Faster than custom code, more flexible than a Zap, and you don't own the ongoing maintenance.
The honest tradeoff: for a two-step Slack notification, use Zapier. For anything with branching logic, mapping tables, or money moving, you need real code or someone running real code for you. The middle ground — a 15-step Zap with error handlers duct-taped on — is where I've seen the most silent failures.
How BizFlowAI approaches this
We build integrations for solopreneurs and small teams who need Shopify to talk to QuickBooks, HubSpot to sync with their billing system, or their inbox to route leads to the right person — without hiring a developer or wrestling with a 20-node Zap. The integrations run on our infrastructure with signature verification, idempotency, retries, and reconciliation jobs already handled. You get the working flow; we handle the plumbing that breaks at 3 AM.
The typical setup: we map out your current process, identify the three or four APIs involved, and ship a running integration in days, not weeks. If you're already drowning in CSV exports or paying for a stack of one-off tools, that's usually the signal it's time to stop patching and start integrating properly.
What to do this week
If you're evaluating whether to integrate two systems, spend 30 minutes on this checklist before writing any code or paying for any connector:
- Open both apps' API docs. Are the endpoints you need documented with example requests and responses?
- Check for webhooks on the source side. Which events fire? Is there a signature scheme?
- Read the rate limit page. Will your expected volume fit inside the free tier?
- Look at the auth flow. API key (fast) or OAuth (more setup, more secure)?
- Sketch the failure modes. What happens if the destination is down for 10 minutes? An hour? A day?
If those five answers are clean, the integration is buildable in a week. If any answer is "the docs don't say" — that's your risk, and no tool, no-code or otherwise, will make it disappear.
Work with BizFlowAI
If you'd rather have this built for you, that's what we do: production AI automation for solo founders and small teams — agents, integrations, and document pipelines that actually ship.
Book a free discovery call — 30 minutes, we map the highest-ROI automation in your workflow. No pitch deck, just engineering.
More guides like this on the BizFlowAI blog.
Frequently asked questions
What is the difference between webhooks and polling?
Polling means your app calls an API on a schedule to check for new data, which is simple but adds latency and wastes calls when nothing changed. Webhooks flip the direction: the source app pushes an HTTP request to your endpoint the moment an event happens, giving near real-time updates. Webhooks are faster and cheaper but require a public, always-on URL, signature verification, and idempotent handling. Polling works with any API, while webhooks only work when the provider supports them.
How do I secure a webhook endpoint?
Always verify the signature that the provider sends in the request headers using HMAC and your shared secret before trusting any payload. Return a 200 response fast and process the work asynchronously in a background job to avoid timeouts and duplicate deliveries. Store every event ID you have processed so repeated deliveries are ignored (idempotency). Store the signing secret in an environment variable or secret manager, never in source code.
Which API authentication method should I use?
Use API keys for simple server-to-server access when you control both sides and can rotate keys safely. Use OAuth 2.0 when your app acts on behalf of an end user, as with Google, HubSpot, or Salesforce integrations. Use HMAC signed requests for webhooks so you can verify the sender and detect tampering. Use JWTs for machine-to-machine auth in microservices where signed, expiring claims are needed.
How do I integrate Shopify with QuickBooks reliably?
Subscribe to the Shopify orders/paid webhook, verify its HMAC signature, then push the order into a queue and return 200 immediately. A background worker looks up or creates the QuickBooks customer, maps line items, creates the invoice, and records the payment. Use exponential backoff on 5xx errors and log 4xx errors for manual review instead of blind retries. Run a nightly reconciliation job that compares Shopify orders against QuickBooks invoices to catch anything the webhook missed.
What are the most common API integration failure modes?
Webhooks arriving twice, out of order, or being dropped during provider outages cause duplicate or missing records if you lack idempotency. Timeouts happen when handlers do too much synchronous work instead of queuing it. Rate limits return 429 errors that must be handled with backoff, and expired OAuth tokens silently break jobs if refresh logic is missing. Leaked API keys committed to Git or shared over Slack are a top security failure — rotate them immediately when exposed.