Skip to Content
TroubleshootingOverview

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:

  1. No published flow subscribes to the event type. Flows match on the exact (source: "event_api", event_type) pair. The send response carries a hint naming the unmatched event types, and GET /api/v1/events/{id}/status reports status: "skipped". Fix the flow’s event_type (or add a wildcard like order.*) and publish.
  2. 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 hint is computed against the published config, so it catches this case too.
  3. A flow matched but a step failed. Check the per-entity error and 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 carries Retry-After: 1 and the X-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. detail carries events_used_this_period and period_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 the Retry-After header, 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 - an identity_guard rejection: too many identity fields changed at once on an existing record, which usually means a source-side join error. Replay requires an explicit force=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 the conflicts block 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:

  1. Check the source exists and is enabled — a flow whose source names a missing or disabled source silently never matches.
  2. Check the flow’s event_type against what the source actually emits (the HubSpot journal emits per-object change types; a poller emits what its jsonpath extraction 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.
Last updated on