Workflow AutomationOperationsn8nMake

Some Records Didn't Sync, but the Run Says Success

When a batch write partly fails, Salesforce sObject Collections still returns 200 OK with success false on the failed items, HubSpot returns 207 Multi-Status with numErrors, and Shopify GraphQL returns 200 with userErrors, so n8n, Zapier and Make record a successful step. The fix is to read the per-record results after every batch write, require that succeeded plus failed equals records sent, and route the failures by a key you control.

Alexey YushkinFounder, GENERAL INFORMATICS2 min read

When some records fail to sync but the run shows success, the batch write almost always returned a success status with the failures listed inside the response body. Salesforce sObject Collections returns 200 OK with "success": false on each failed item, HubSpot batch endpoints return 207 Multi-Status with a numErrors count, and Shopify's GraphQL Admin API returns 200 with a userErrors array. n8n, Zapier and Make decide pass or fail from the status code, so the step goes green, and the missing records are found weeks later by a person, not by the automation.

The status code describes the request, not the records

A batch endpoint answers two different questions, and the HTTP status only answers the first one. Was the request well formed and accepted? Yes, here is a 200. Did every record in it get written? That answer lives in the body, one entry per record.

Salesforce says this in so many words: if the request is well formed, the API returns 200 OK, the success flag shows per item, and error information comes back in the errors array (Salesforce REST API Developer Guide, checked October 2026). Its allOrNone parameter defaults to false, which means one malformed ID fails one record and the other 199 are written.

Now look at what the workflow tool does with that. The n8n HTTP Request node documentation states that by default the node returns success only when the response comes back with a 2xx code. Make's HTTP module stops the scenario on a 4xx or 5xx when you set "Return error if HTTP request fails" to Yes. A 207 is a 2xx. A 200 with three failed records inside is a 2xx. Neither tool opens the body to look.

This is the write-side twin of the read-side problem in syncs that only pull the first 100 records. There, the run requested less than it should have. Here, the run sent everything and the destination quietly declined part of it. In both cases there is no error to alert on, and every step in the execution history looks the same as a perfect run.

Where each API puts the failed records

Each vendor reports a partial failure in a different place and links the failure back to your input in a different way. The second column is what your workflow sees. The last column is the part most implementations get wrong.

EndpointStatus on partial failureWhere the failures areHow a failure maps back to your record
Salesforce sObject Collections (up to 200 records)200 OKArray of SaveResults, success: false plus errors[] with statusCode and fieldsBy position only. Results come back in request order, and a failed item has no id
Salesforce Bulk API 2.0 ingest jobJob state Failed, numberRecordsFailed above 0, good rows keptSeparate failedResults CSV with sf__Error and sf__IdBy your own columns, which the CSV echoes back. Row order is not guaranteed
HubSpot CRM batch create207 Multi-Statusresults[], numErrors, errors[]By objectWriteTraceId, which you must send on every input
Shopify GraphQL Admin API mutation200 OKuserErrors with field path and messageOnly if your mutation selects userErrors. Shopify returns them only when requested
Google Sheets spreadsheets.batchUpdateError statusThe whole requestNothing to map. If any request is invalid, nothing is applied

Sources: vendor documentation for each endpoint, checked October 2026.

Two rows deserve a closer look.

Salesforce's position rule is the one that bites. A failed SaveResult in the documented example has no id field at all, just success: false and the error. If your workflow splits the response into items and filters for failures, you now hold an error with nothing that says which of your records it belongs to. You have to zip the response array against the request array by index before you filter, while both are still in the same order.

Shopify's rule is the quiet one. The Admin API documentation says many errors that would be a 4xx or 5xx in REST come back as an HTTP 200 instead, and that to see mutation validation details you must request userErrors. A mutation copied from a tutorial that selects only the created object's id gets back null for that object and no explanation. Nothing failed, as far as the workflow knows.

Google Sheets is in the table as the contrast. Its batchUpdate is all or nothing, which means the failure is loud. Loud failures are the easy kind.

What it looks like in a real run

Here is an illustrative case, the shape we see often, not a measured one. A nightly job pushes 200 changed contacts from a booking system into Salesforce through sObject Collections. Three of them have an email that a duplicate rule rejects, and one has an ID copied from the wrong object.

The response is 200 OK with 200 entries. 196 say success: true. Three say DUPLICATES_DETECTED. One says MALFORMED_ID. The n8n execution is green, the step output shows 200 items, and anyone glancing at it reads "200 synced."

The next night, those four contacts have not changed in the source, so the incremental job does not pick them up again. They stay stale in Salesforce until someone edits them by hand. Batch failures do not repeat on their own, which is why they last. The record that failed once falls out of every later "changed since" window.

The check to put after every batch write

The fix is one step, placed right after the batch call, before anything downstream runs. It does four things.

First, normalize the response into one row per record you sent, carrying your own key. For Salesforce collections, zip by index. For HubSpot, set objectWriteTraceId to your source record ID when you build the request, so the error's context hands it straight back. For Bulk API 2.0, include your external ID as a column in the upload, because the failed-results CSV echoes your original fields. For Shopify, select userErrors on every mutation and keep your key alongside each call.

Second, count. Records sent must equal records succeeded plus records failed. If the numbers do not add up, the response is truncated or malformed, and the run should fail outright. This is the same discipline as reconciling counts on a read, applied to a write.

Third, split the failures by whether a retry can help. A row lock or timeout can succeed on a second attempt. DUPLICATES_DETECTED, MALFORMED_ID, a required field left blank, or a picklist value the destination does not accept will fail identically every time. Retry the first group once. Send the second group to a review queue with the error message and the source record link, as covered in when an automation should retry.

Fourth, write the outcome to the run record: sent, succeeded, failed, and the failed keys. If you already keep a run record for every automation, these are three numbers and a list. Then make the run end in a warning state, not success, whenever failed is above zero. In n8n that can be a Stop and Error node behind an IF on the failed count; in Make, a filtered route on the failed count that ends in an error after the good records are handled.

That last part matters more than it sounds. A run that partly failed and says so gets looked at. A run that partly failed and says success never does.

When to make the batch all or nothing instead

Salesforce lets you flip allOrNone to true, and the Bulk API 2.0 docs are explicit that successfully processed job data is not rolled back, so the choice is real and it is yours to make per flow.

Use all or nothing when the records in one request depend on each other. An order header and its line items, an account and the contacts that point at it, a parent and children written in one call. A half-written group is worse than none, the same reasoning as writing line items in one request.

Keep it off for independent records. A contact sync where one bad email blocks 199 good updates trades a small, visible problem for a large one.

Either way, read the body. With allOrNone: true, Salesforce still returns 200 OK, and every otherwise valid record comes back marked ALL_OR_NONE_OPERATION_ROLLED_BACK. A workflow that does not parse the results will report that nothing went wrong on a run that wrote nothing at all.

What to do next

Open the last successful run of each automation that writes in batches and look at the raw response body of the batch step, not the step's status. If you find a success: false, a numErrors above zero, or a non-empty userErrors, you have records that never arrived, and the check above is the next thing to build. If you would rather have someone audit every batch write in your stack and wire the reconciliation once, that is part of how we build workflow automation systems, and you can tell us what your sync looks like.

Frequently Asked Questions

Batch write endpoints report per-record failures inside the response body, not in the HTTP status. Salesforce sObject Collections returns 200 OK with success false on each failed item, HubSpot returns 207 Multi-Status, and Shopify GraphQL returns 200 with userErrors. n8n, Zapier and Make treat any 2xx response as a successful step, so the run stays green.

For sObject Collections, read the response array in order. Salesforce returns one SaveResult per record in the same order you sent them, and a failed item carries no id, so position is the only link back to your input. For Bulk API 2.0, call the failedResults endpoint, which returns a CSV with sf__Error, sf__Id and your original columns, in no guaranteed order.

Turn it on when the records in one request only make sense together, such as an order and its line items. Leave it off for independent records like a contact sync, where one bad email should not block 199 good ones. Either way you still have to read the results, because allOrNone true also returns 200 OK and marks the rolled-back records with ALL_OR_NONE_OPERATION_ROLLED_BACK.

Only the ones that failed for a transient reason, such as a lock or a timeout. Validation failures like DUPLICATES_DETECTED, MALFORMED_ID or a missing required field fail the same way on every retry. Send those to a review queue with the error message attached, and retry the rest once.

HubSpot's error-handling documentation ties 207 Multi-Status on batch create to multi-status error handling, which you enable by giving each input a unique objectWriteTraceId. The 207 body carries results, numErrors and an errors array, and each error's context names the objectWriteTraceId of the input that failed. Without trace IDs you lose the clean mapping from error to record.

SOURCES & CITATIONS

  1. Update Records Using sObject Collections, REST API Developer Guide — Salesforce Developershttps://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_composite_sobjects_collections_update.htm
  2. Get Job Failed Record Results, Bulk API 2.0 Developer Guide — Salesforce Developershttps://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/get_job_failed_results.htm
  3. Error handling, HubSpot API reference — HubSpot Developershttps://developers.hubspot.com/docs/api-reference/latest/error-handling
  4. GraphQL Admin API reference, status and error codes — Shopifyhttps://shopify.dev/docs/api/admin-graphql

About Alexey Yushkin

Alexey is the founder of GENERAL INFORMATICS LLC. He designs and ships AI and automation systems for businesses and operators across the US.

Connect on LinkedIn

Related reading

Want this kind of system in your business?

We build practical AI and automation systems for operators. Send us your current workflow and we will show you what to automate first.