Troubleshooting
Symptom-first index of the failure modes you are most likely to hit, what each one actually means, and where the fix lives. Every entry is grounded in the platform’s real contracts — status codes, response hints, and dashboard surfaces — not folklore.
The event is accepted (202) but nothing appears in HubSpot
The 202 only means “queued”. Three causes, in order of likelihood:
- No published flow subscribes to the event type. Flows match on the
exact
(source: "event_api", event_type)pair. The send response carries ahintnaming the unmatched event types, andGET /api/v1/events/{id}/statusreportsstatus: "skipped". Fix the flow’sevent_type(or add a wildcard likeorder.*) and publish. - The flow exists only in the draft. Nothing applies until you go
through Review changes → Publish — the draft is never executed. See
draft → publish. The send-time
hintis computed against the published config, so it catches this case too. - A flow matched but a step failed. Check the per-entity
errorand step trace in/status— each entity row names the failing step and the classified error.
When the record is written but with unexpected values, use
GET /api/v1/events/{id}/verify:
it re-reads HubSpot and diffs it property-by-property against what your
published flow projects.
401 Unauthorized on /api/v1/events
The x-api-key header is missing, malformed, or the key was revoked. Keys
are connector-scoped (ps_live_...); the secret is shown once at creation.
Mint a new one from connector → Settings → API keys.
423 Locked on /api/v1/events
The connector is paused or in error: ingestion is deliberately blocked,
nothing is queued. Resume the connector from the dashboard. Pause stops both
sides — event intake and source polling.
A new connector paused after its first records has no Resume action: an owner lets it continue instead, see A new connector paused after its first records.
My events get 429 Too Many Requests
Three unrelated causes share this status. The body is {"detail": ...}: a
plain string for the rate limit, an object with an error field for the
other two:
"Rate limit exceeded for org"- the org sent more requests in the last second than its tier allows, across all its connectors and keys. The response carriesRetry-After: 1and theX-RateLimit-*headers: slow down and resend. Per-tier limits: the 429 section of the Event API reference.quota_exceeded- the org’s monthly event quota for its tier is exhausted.detailcarriesevents_used_this_periodandperiod_resets_at. The monthly events of each plan: Plan limits.connector_flow_breaker_open- every flow that would process this event’s type is temporarily paused after repeated failures (see Syncs stopped on one flow below). The event was rejected, not queued: wait the number of seconds in theRetry-Afterheader, then resend it (that resend is what lets the flow attempt its own recovery). Full body shape and the trigger conditions: the 429 section of the Event API reference.
None of them needs a support ticket by itself: a rate limit clears within
the second, a quota issue is fixed by upgrading or waiting for the reset
date, and a breaker-driven 429 clears on its own once the underlying flow
problem does.
A request gets 402 Payment Required
The organization reached a limit of its plan (connectors, event sources,
team seats, HubSpot portals, custom object types, the trial’s daily
assistant sessions) or asked for something its plan does not include
(plugins, test-mode connectors, SLA reporting). Nothing was changed. The
code is in detail.error, with limit and usage where something is
counted and an upgrade_url to the dashboard billing page.
Read GET /api/v1/billing/me to see every counter against its limit, then
either free a slot (delete an unused connector or source, revoke an open
invitation, disconnect a portal) or upgrade. Plugins and SLA reporting come
with Enterprise and Custom-Dedicated, through sales. Each code, the requests
that answer it and its way out:
Plan limits.
503 Service Unavailable
A transient platform failure while accepting the event
(detail.error: "event_ingestion_unavailable") or storing its attachment
(attachment_store_unavailable). Nothing was enqueued, so the request is
safe to retry with backoff; GET /api/health/data-plane says when intake is
back.
This is the ingest call failing. A transient failure after the event was accepted is retried by the platform on its own schedule, documented with its real numbers in Reliability: retry, failures, replay.
/status reports partial_failure
At least one entity failed, or only part of a multi-entity event synced. Read
the per-entity rows in the same response: each carries action, status,
hubspot_id, and error detail. partial_failure is also the terminal
fallback for an event past the stuck deadline that has at least one entity
already reported: the entities that never reported are counted as the gap
between entities_total and entities_done.
If the event is past the same deadline but no entity has reported at all,
/status reports delayed instead, not partial_failure: that shape means
the event is simply still waiting to be processed (a backlog), not a crash.
See the hint field for how long it has been queued, and poll
GET /api/v1/events/{id}/status
for the full status ladder.
A record ended up parked, failed, or quarantined
Those are record-level states in the connector’s sync events, one row per
record, and they are distinct from the event-level /status ladder above.
The short version:
parked- a transient failure (typically a dependency that had not synced yet) used up its 5 delivery attempts without ever writing anything. Not a bug: give it time and replay it.failed- a permanent error, or a mix that includes one. A replay alone will not fix it; fix the config or the payload first.quarantined- anidentity_guardrejection: too many identity fields changed at once on an existing record, which usually means a source-side join error. Replay requires an explicitforce=true.
In all three cases the row stays visible while it is kept and, when it
carries a captured payload, it can be replayed: the row has a replayable
flag, and
POST /api/connectors/{id}/sync-events/{event_id}/replay re-runs it against
the currently published config, so “fix, publish, replay” works. Full
semantics, retry numbers and the record-mode range replay:
Reliability: retry, failures, replay.
Syncs stopped on one flow but the rest of the connector works
A flow’s circuit breaker trips for one of three reasons: 3 consecutive fatal
outcomes (401 / 403, e.g. a revoked token, a missing OAuth scope, or a
wrong form GUID, where retrying only hammers the target), 5 consecutive
records that exhausted their retries without completing, or a failure rate
over half on at least 20 recent executions. Whichever the cause, only that
flow is skipped while its siblings keep syncing.
Check GET /api/connectors/{id}: the health block names the tripped
flow(s), the reason, the last error, and when the next automatic recovery
attempt happens. Fix the credential or the flow, then either wait for that
automatic attempt or trigger it immediately from the dashboard’s “Retry now”
(POST /api/connectors/{id}/flows/{flow_name}/retry). Even when every
enabled flow has tripped at once, the connector itself is not suspended:
health.state reports error for that case, but each event type still
answers on its own terms (429 while its flow is open, per the section
above) rather than a blanket 423. 423 is reserved for a connector that is
actually paused or in error for a reason unrelated to any breaker; setting
it back to active still closes every flow’s breaker at once. See
Circuit breaker.
A new connector paused after its first records
The connector page reads Paused after its first N records and the organization’s owners got an email about it. This is the first-sync write cap, not a failure: on the organizations where the first-sync review is enabled, a new connector stops after its first 500 records written to HubSpot so someone can check them before the rest of the sync follows. It is not on by default, and Enterprise and Custom-Dedicated organizations are never under it.
Open a few of the records in HubSpot. If they are wrong, fix the
configuration and publish it first: the events kept during the pause run
against the configuration published when they run. Then an owner clicks
Continue syncing on the connector page, or calls
POST /api/connectors/{id}/write-cap/release with a signed-in session
(members and organization API keys get 403 owner_required). The connector
sends the kept events again, then reopens on its own, usually within a few
minutes, and the cap never pauses it again. Single events sent to the Event
API while it waits get 423 and must be sent again; bulk import rows held
more than 3 days end failed and can be replayed.
PATCH on the connector’s status answers 409 write_cap_release_required
before the release and 409 write_cap_resume_in_progress while the kept
events are being sent. What counts toward the 500, what is kept, and the
write_cap block of GET /api/connectors/{id}:
First-sync write cap.
The Conflicts tab is empty and you expected conflicts
Almost always correct behavior, not a broken engine:
- Under
source_wins(the default, including when theconflictsblock is absent) no detection runs at all — zero rows is the strategy working. - Under
target_wins/most_recent, only a property whose last HubSpot edit came from outside plugsync is ever arbitrated. If plugsync is the only writer, every write is conflict-free by definition.
GET /api/connectors/{id}/conflicts/count returns policy_active and
never_matched so you can confirm the policy is live without waiting for a
real conflict. Full semantics (including the source_modified_path
envelope-vs-record trap):
conflicts reference.
An outbound flow never fires
Outbound events enter through an event source (HubSpot journal, REST poller, scheduled tick, file), not through the Event API:
- Check the source exists and is enabled — a flow whose
sourcenames a missing or disabled source silently never matches. - Check the flow’s
event_typeagainst what the source actually emits (the HubSpot journal emits per-object change types; a poller emits what itsjsonpathextraction names).
See event sources.
Field mapping errors in the dashboard
The mapping editor validates against real captured payloads: every event
ingested is sampled, so the editor knows the actual shape of $.payload.*.
A mapping that references a path absent from the samples is flagged inline.
Send one representative event first, then map — see
getting started, step 5.
Draft edits seem to have no effect
By design. All writes (dashboard, AI assistant, MCP, CLI, setup scripts)
land in the draft, and nothing changes until the draft is published. A
publish is always an explicit act: Publish from Review changes in the
dashboard, plugsync push --publish from the CLI, the publish endpoint of
the API, or the MCP server’s publish tool. The in-app AI assistant cannot
publish. The published version is usually applied to incoming events within
a few seconds (see Reliability and
draft → publish). If publish
returns a 409, someone else published since
you started editing: reset your draft or keep editing and re-review. See
draft → publish.
Still stuck?
- The API error catalog lists every error code the public API returns, which field carries it, and what to do about it.
- The CLI can pull connector status, logs, and
configs from the terminal (
plugsync --help). - Anything that looks like a platform bug: write to the support address listed in the Terms of Service. Enterprise and Custom-Dedicated orgs can also use the support contacts in their order form.