A new customer appears in your store but never reaches the CRM. A phone number updated in the CRM still shows the old value in your billing app. Or the same contact shows up three times. Often the integration dashboard still says “connected,” and the gaps only surface when someone notices a missing lead or a wrong invoice.
CRM sync problems rarely have one cause. The data has to be sent, accepted, matched to the right record, and written in the right format, and each step can fail on its own. This guide covers the causes common to most CRMs and integration tools, how to find yours, and how to reconcile records once the connection is fixed.
Quick diagnostic checklist
- Is the problem every record, some records, or only records created after a certain date?
- Does the integration’s run history or log show the record at all? If so, what HTTP status code came back?
- Was a password, user account, app permission, or connected account changed recently?
- Were fields renamed, deleted, or made required in either system?
- Which field is used to match records (email, external ID, phone), and is it filled in on the missing records?
- Is the sync one-way or two-way, and which system should win when both change?
- Did a large import or bulk update happen just before the problem started?
Common causes of CRM data not syncing
1. Expired or revoked authentication
Most CRM APIs use OAuth 2.0. Access tokens are short-lived (the token response’s expires_in gives the lifetime in seconds), and the integration uses a refresh token to get a new one, per RFC 6749. If the refresh token is revoked or expires (depending on the platform, this can happen when the connecting user is deactivated, uninstalls the app, or changes security settings), the refresh request fails with an invalid_grant error, and every later API call returns 401 Unauthorized. The server may also issue a new refresh token on each refresh; an integration that does not save it can break days after setup.
2. Missing permissions
A 403 Forbidden response is different from a 401. MDN’s status code reference explains that with 403 the server knows who the client is but refuses access. In CRM terms, the connected user or app lacks the scope, role, or object permission to read or write that record type. It often appears after an integration authorized by an admin is reconnected by a regular user.
3. Rate limits
APIs cap how many requests a client can make in a time window. When the cap is hit, the server returns 429 Too Many Requests, sometimes with a Retry-After header giving the number of seconds to wait or a date, as described on MDN’s 429 and Retry-After pages. Limits vary by vendor, plan, and endpoint. HubSpot’s API usage guidelines, for example, set per-10-second limits by app type and subscription, with a separate limit for search. Bulk imports are the classic trigger: normal days sync fine, and import day fails.
4. Validation and field mapping errors
A 400 Bad Request or 422 Unprocessable Content response usually means the request arrived but the data did not pass the receiving system’s rules. Typical examples: a blank required field, a dropdown value that does not exist on the other side (“CA” versus “California”), a renamed field the mapping still points to, or “$1,200.00” sent to a number field. These errors only hit records with that data, so they look random until you read the response body, which usually names the field.
5. Record matching and duplicates
Every sync has to decide whether an incoming record is new or an update. If it matches on email and a contact has no email, or two contacts share a company inbox, the integration either creates a duplicate or updates the wrong person. The most reliable pattern is a stable unique identifier stored in both systems. Salesforce’s upsert by external ID illustrates the logic: no match creates a record, one match updates it, and multiple matches return an error with HTTP 300 rather than guessing.
6. Duplicate or out-of-order webhook deliveries
Webhooks are usually delivered at least once, not exactly once. Stripe’s webhook documentation is a clear example: it retries failed deliveries for up to three days in live mode, warns that endpoints may receive the same event more than once, and states that event order is not guaranteed. A receiver that creates a record per event turns retries into duplicates, and one that applies events in arrival order can let an older update overwrite newer data. Slow processing before responding can also cause timeouts, and the sender retries events you already handled.
7. Two-way sync conflicts and loops
When both systems can edit a field, you need a rule for who wins; otherwise the last write wins, even if it is stale. Worse, A’s update syncs to B, B’s “record updated” event syncs back to A, and the loop burns through rate limits. A 409 Conflict from an API that uses record versions is the explicit version of this problem.
8. Date, time zone, and format mismatches
A date sent as 03/04/2026 means March 4 in the US and April 3 in the UK. A timestamp without an offset gets interpreted in whatever zone the receiving server assumes, which can shift appointments or push records outside a “modified since” window. RFC 3339, a profile of ISO 8601, avoids this by requiring an explicit offset, as in 2026-09-21T14:30:00Z or 2026-09-21T10:30:00-04:00.
9. Pagination and incremental sync gaps
List endpoints return results in pages. An integration that reads only the first page, or stops when a page comes back short, silently skips records. Incremental “modified since last run” syncs miss changes if the checkpoint is saved before the run finishes or if time zones differ.
Step-by-step troubleshooting
- Pick one specific missing record. Note its ID, when it was created or changed, and what should have happened.
- Find it in the integration’s history. In Zapier, Make, n8n, or a custom log, search for that record’s time window. No run at all points to the trigger or webhook. A run with an error points to the request. A successful run with no visible result points to matching or mapping.
- Read the status code and response body. 401 means re-authenticate; 403 means check the connected user’s permissions and app scopes; 404 means the record or endpoint ID is wrong or the record was deleted; 400 or 422 means read the field named in the error; 429 means you are over the limit; 5xx means the receiving service or a gateway failed, and a retry is usually appropriate.
- Reproduce with a test record. Create a clearly labeled test contact in a sandbox or test pipeline, not a real customer, and push it through. Change one variable at a time: a blank email, a long name, a non-US phone number.
- Compare the payload with the field mapping. Look at the data the sender actually produced and the fields the receiver expects. Confirm that each mapped field still exists, that picklist values match exactly, and that data types agree.
- Check the matching key. Search the destination for the record by email, external ID, and name. Finding two records, or a record matched to the wrong contact, means the dedupe logic needs a stable ID.
- Check volume and timing. Failures clustered around imports suggest 429s; confirm the integration backs off.
- Re-run safely. Replay failed records only after you have confirmed the fix and that the write is idempotent, so replays update rather than duplicate.
Platform-specific guidance
No-code tools. If the sync runs through Zapier, the step-by-step checks in Zapier not sending data between apps apply. For self-hosted or cloud n8n workflows, see n8n workflow not running. Both show each step’s request and response, including the status code.
Idempotency keys. Some APIs accept a unique key so a retried create cannot run twice; Stripe’s idempotent requests return the saved first result for repeats, and keys can be pruned after 24 hours. Where a CRM lacks this, upsert on an external ID or store processed event IDs.
Handling retries without creating duplicates
This JavaScript sketch skips already-processed events, retries only rate limits and server errors, honors Retry-After, and logs everything else. The endpoint and fields are placeholders; your CRM’s upsert endpoint and Idempotency-Key support will differ.
async function syncOrder(event, store, log) {
if (await store.hasProcessed(event.id)) return 'duplicate-skipped';
const payload = {
externalId: event.order.id, // stable ID stored in both systems
email: event.order.email?.trim().toLowerCase(),
total: Number(event.order.total),
createdAt: new Date(event.order.created_at).toISOString() // UTC, ISO 8601
};
for (let attempt = 1; attempt <= 5; attempt++) {
const res = await fetch('https://api.example-crm.com/v1/orders/upsert', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': event.id // only if the API supports it
},
body: JSON.stringify(payload)
});
if (res.ok) { await store.markProcessed(event.id); return 'ok'; }
if (res.status === 429 || res.status >= 500) {
const ra = res.headers.get('Retry-After');
let waitMs = Math.min(60000, 1000 * 2 ** attempt); // exponential backoff
if (ra) waitMs = /^\d+$/.test(ra) ? Number(ra) * 1000 : Math.max(0, Date.parse(ra) - Date.now());
await new Promise(r => setTimeout(r, waitMs));
continue;
}
// 4xx: retrying will not help; log for review
log.push({ eventId: event.id, status: res.status, body: (await res.text()).slice(0, 500) });
return 'failed';
}
log.push({ eventId: event.id, status: 'retries-exhausted' });
return 'failed';
}Keep real tokens in a credential manager or environment variables, never in shared code.
Sample workflow (hypothetical): syncing e-commerce orders to a reporting spreadsheet with error handling. Illustration only, not a client project.
| Trigger | An “order created” webhook from the e-commerce platform. |
|---|---|
| Applications | E-commerce platform, an integration layer (n8n, Make, Zapier, or custom code), a reporting spreadsheet, Slack or email, and a failure log. |
| Data inputs | Order ID, event ID, customer email, line items, total, currency, and a timestamp with offset. |
| Processing logic | Skip already-processed event IDs, validate required fields and totals, normalize email, convert time to UTC, and upsert a row keyed on order ID. |
| Expected outputs | One row per order, never duplicated on retries, plus a team notification. |
| Failure points | Expired credentials (401), a renamed sheet, duplicate deliveries, rate limits during sales, malformed totals. |
| Error handling | Acknowledge the webhook quickly, queue processing, retry 429 and 5xx with backoff, and log other failures with order ID, status, and message. |
| Validation and testing | Send test orders, replay one event twice to confirm a single row, test a missing email, and compare daily row counts with the store’s order report. |
| Business application | Daily sales reporting without manual exports, and a visible list of anything that failed. |
A native connector may be enough to append new orders to a sheet. Custom logic is needed for deduplication, validation, row updates, or reconciliation.
Reconciling records after the fix
Fixing the connection does not recover records that failed while it was broken. A reconciliation pass does:
- Define the window, starting a little before the first logged failure.
- Export both sides for that window, following every results page, with the shared ID and last-modified time.
- Compare: source-only records (missing), records with different values (stale), and destination-only records (orphans or duplicates).
- Replay missing and stale records through the fixed, idempotent sync in small batches that respect rate limits.
- Review duplicates manually before merging; merges are hard to undo.
- Re-run the comparison until the counts match or every difference is explained.
How to confirm the issue is fixed
- A new test record created in the source appears in the destination with every mapped field correct.
- Replaying the same event produces an update, not a second record.
- The log shows no new 401, 403, or 422 errors over several days.
- Reconciliation counts for the affected window match.
- In a two-way sync, editing a test record in one system changes the other once, with no update loop.
Common mistakes to avoid
- Matching on name or on an optional field such as email when a stable ID is available.
- Retrying every error, including 400 and 422, which will never succeed without a data change.
- Replaying failed records before making the write idempotent.
- Letting both systems edit the same field with no rule for which one wins.
- Testing fixes on live customer records instead of test data or a sandbox.
Prevention and monitoring
- Document each integration’s field mapping, matching key, direction, and conflict rule, and review it before renaming CRM fields.
- Connect integrations with a dedicated service account where the platform allows it, so staff changes do not revoke access.
- Alert on failures: a message when an error log gets a new entry, and a daily summary of error counts by status code.
- Schedule a lightweight weekly reconciliation that compares record counts for the past seven days.
Syncs that span several apps or run two-way are usually worth designing as a proper integration. Our custom API integration services cover authentication, mapping, validation, logging, and reconciliation.
Still having trouble with your CRM sync?
Tell Modern Streamline which applications you’re connecting, what data should move between them, and what is happening instead. We can review your requirements and discuss an appropriate solution.
FAQ
Why do some contacts sync and others don’t?
Partial failures usually come from the data. Records with a blank matching field or an unrecognized dropdown value get 400 or 422 errors while others pass. Compare a failing record with a working one.
Why does my integration create duplicate records?
The sync is not matching incoming data to existing records, or it processes retried webhooks as new events. Use a stable unique ID for upserts and track processed event IDs.
My integration says “connected.” Why is nothing syncing?
Connection status often reflects the last successful authorization, not whether current requests succeed. Check the run history for 401 or 403 responses, which usually mean a revoked token or changed permissions.
Should I use two-way sync?
Only if both teams truly need to edit the same fields. One-way sync with one system of record per field is simpler to maintain and troubleshoot.
Sources
- MDN: HTTP response status codes
- MDN: 429 Too Many Requests
- MDN: Retry-After header
- MDN: 409 Conflict
- IETF RFC 6749: The OAuth 2.0 Authorization Framework
- IETF RFC 3339: Date and Time on the Internet: Timestamps
- Stripe Docs: Receive Stripe events in your webhook endpoint
- Stripe API Reference: Idempotent requests
- HubSpot Developers: API usage guidelines and limits
- Salesforce REST API Developer Guide: Insert or update (upsert) a record using an external ID