Skip to Content
ReferenceEvent APIOverview

Event API

The Event API is how an external system pushes events into a connector in real time: POST /api/v1/events accepts one business event, persists it, and enqueues it for the flow worker. Flows subscribe to (source: "event_api", event_type) pairs — see the flow authoring guide.

This page covers the real-time path. For day-0 loads and large re-syncs (thousands of records in one shot) use the bulk import endpoint instead.

Authentication

Every request carries a connector API key in the x-api-key header. Keys are scoped to one connector, look like ps_live_..., and are minted from the dashboard (connector → Settings → API keys) or via the API:

MethodPathEffect
POST/api/connectors/{connector_id}/api-keysCreate a key. The full secret is returned once, at creation.
GET/api/connectors/{connector_id}/api-keysList keys (prefix + metadata only).
DELETE/api/connectors/{connector_id}/api-keys/{key_id}Revoke a key.

These management endpoints authenticate as your dashboard user, not with the event key itself.

Send an event

curl -X POST https://app.plugsync.com/api/v1/events \ -H "x-api-key: ps_live_..." \ -H "content-type: application/json" \ -d '{"event": "order_created", "data": {"order_id": "A-1001", "total": 129.9}}'

Request body

FieldTypeRequiredNotes
eventstringyesThe event type. Flows match on (source: "event_api", event_type); wildcards like order.* are declared on the flow, not here.
dataobjectyes*The business payload. Inside flow manifests this exact object is addressed as $.payload.*.
timestampISO 8601 datetimenoThe business time the event occurred (not the send time). Used as occurred_at by most_recent conflict arbitration when no source_modified_path resolves. Naive datetimes are treated as UTC. Absent, the ingest instant is used.
storeIdstringnoMulti-brand routing. Can also be sent as the x-store-id header; falls back to the connector’s default store.
entitiesarrayno*Legacy multi-entity shape (max 200): each entry enqueues one {entity.type}_synced event. Prefer event + data for new integrations.

*Send either data (canonical) or entities (legacy). A stray payload key is rejected with a 422 — the business payload goes under data; the $.payload.* spelling exists only inside flow manifests.

Response

202 Accepted — the event is queued, not yet processed:

{ "event_id": "8b1f...", "status": "processing", "entities_count": 1, "hint": null }

hint, when present, is an advisory string worth logging. It fires in two cases:

  • No subscribed flow: no published flow listens on (event_api, <your event_type>). The event is accepted but nothing will sync until a matching flow is published. This is computed against the published config, so a draft-only flow still triggers the hint.
  • Payload too large for the real-time path: a legacy entities payload above 50 entries gets pointed at bulk import.

Errors

StatusMeaningWhat to do
401Missing, malformed, or revoked API key.Check the x-api-key header; mint a new key if revoked.
422Body validation failed (e.g. a stray payload key, missing event).The detail message states the exact field.
423The connector is paused or in error: ingestion is blocked.Resume the connector from the dashboard.
429Monthly event quota exceeded for the org’s tier.The detail carries events_used_this_period and period_resets_at.
503Transient infrastructure failure (ingest queue or attachment store unavailable).Retry with backoff; nothing was enqueued.

These are the errors of the ingest call. Once an event is accepted with a 202, what happens to it if a flow step fails (how many times it is retried, which terminal state it lands in, how to replay it) is covered in Reliability: retry, failures, replay.

Check processing status

curl https://app.plugsync.com/api/v1/events/{event_id}/status \ -H "x-api-key: ps_live_..."

Returns per-entity progress (entities_total / entities_done / entities_failed / entities_skipped) plus a row per entity with action, status, hubspot_id, and error detail. The overall status:

StatusMeaning
processingNot all entities have reached a terminal state yet.
delayedThe event has been queued longer than the stuck deadline (PLUGSYNC_EVENT_STATUS_STUCK_DEADLINE_SECONDS, default 600s) but no entity has been processed at all. This is a slow FIFO backlog (queue depth, other-tenant contention), not a failure: keep polling. The hint field explains how long the event has been queued.
completedEvery entity synced successfully.
skippedEvery entity was a no-op: no published flow matched. Nothing was written.
partial_failureAt least one entity failed, or only part of the event synced (the rest skipped), or the event is past the stuck deadline with at least one entity already reported.

delayed and partial_failure both fire off the same stuck-deadline check, but they mean different things: delayed means the event has not been picked up by a worker at all yet (nothing to worry about beyond queue latency); partial_failure past the deadline means at least one entity did get processed but the rest never reported (worth investigating). Do not treat delayed as a failure state in your integration’s error handling: it is transient, exactly like processing.

Verify a processed event

curl https://app.plugsync.com/api/v1/events/{event_id}/verify \ -H "x-api-key: ps_live_..."

Reads the current HubSpot record back and diffs it against what the event’s own published flow projects for its payload: one entry per entity with status of ok / diff / absent / error / not_synced / not_verifiable and per-property differences. Call /status first — this endpoint does not wait for processing to finish.

not_verifiable covers form-only flows (a single submit_form step): the submission was accepted, but HubSpot resolves the contact itself (no record id and no identity mapping), so there is no CRM record to read back and compare.

Bulk import

POST /api/v1/events/bulk takes up to 5000 entities per request (10 MiB combined) and processes them as a chunked job with a terminal, job-level status — built for day-0 loads and migrations, not the per-event real-time path:

{ "entity_type": "contact", "external_id_property": "external_id", "idempotency_key": "initial-load-2026-07", "entities": [ { "external_id": "C-1", "email": "a@example.com" }, ... ] }
  • Idempotency: replaying the same idempotency_key with an identical body returns the original job; a reused key with a different entity count is rejected with 409 body_mismatch.
  • Progress: GET /api/v1/events/bulk/{job_id}/status (job counters) and GET /api/v1/events/bulk/{job_id}/rows (per-row ledger, filterable).
  • Row replay: POST /api/v1/events/bulk/{job_id}/rows/{reference}/replay reprocesses one failed row through the same pipeline and reconciles the outcome into the same ledger entry.
  • Quota: gated upfront on the org’s monthly event quota (429 with the same detail shape as the real-time path); rate limits are surfaced in the X-RateLimit-* response headers.
  • Larger migrations (e.g. ~40k rows) split into consecutive requests of up to 5000 rows each.
Last updated on