Before You Begin
| Concept | Definition |
|---|---|
| HubSpot daily limit | The 24-hour rolling cap on API calls to your HubSpot account, set by your HubSpot edition. |
| HubSpot burst limit | The 10-second-window cap that protects HubSpot from spikes; usually 100 to 190 calls per 10 seconds depending on edition. |
| Chert daily cap | The per-project per-day cap on first-touch sends, configured in Settings → Auto-dial. |
| Idempotency key | A stable token that lets a retry of the same logical operation return the prior result instead of re-sending. |
HubSpot API Calls Made by Chert
Each integration surface consumes a known number of HubSpot API calls.| Surface | Per event | Endpoints |
|---|---|---|
| Sidebar card load | 1 | GET /crm/v3/objects/contacts/{id} |
| Sidebar card send | 1 | GET /crm/v3/objects/contacts/{id} |
| Workflow custom action | 0 | The action callback delivers the data; Chert does not read back. |
| Contact creation auto-send | 1 | GET /crm/v3/objects/contacts/{id} |
| Reply push | 2 | POST /crm/v3/objects/communications, PUT /crm/v4/objects/communications/{id}/associations/default/contacts/{contactId} |
| OAuth token refresh | 1 | POST /oauth/v1/token (does not count against CRM daily limit) |
Chert Send Capacity
| Limit | Default | Configurable |
|---|---|---|
| Project daily cap | Set per project during onboarding | Yes, in the Chert console |
| Per-phone-line minimum gap | 10 minutes between first-touch sends | No |
| Per-phone-line first-touch cap | 30 sends per phone line per day | No |
| Sending window | Per project, in the project’s timezone | Yes |
| Auto-dial enabled | Off until configured | Yes |
ok = false with error = "daily cap reached". The sidebar card
returns the same shape and renders an inline notice. The contact
creation webhook logs the event with status failed and skips the
send.
Every HubSpot-facing failure body now also carries a numeric code,
a generic human message, a retryable boolean, and a trace_id
alongside ok: false / error. The numeric code follows the shared
Messaging API error scheme. All of these routes return
HTTP 200 for logical failures — see Retry Semantics below.
Retry Semantics
From HubSpot to Chert
All four HubSpot-facing routes return HTTP200 for every logical
failure — including signature verification failures — so HubSpot’s
retry-on-non-2xx behavior never fires on a per-event error. The failure
is signalled in the body (ok: false with code / message /
retryable / trace_id, or outputFields.ok: false for the workflow
action). Branch on the body, not the HTTP status.
| Surface | HubSpot retry behavior | Chert response |
|---|---|---|
app.uninstalled webhook | Retries on 5xx with exponential backoff, up to roughly 24 hours. | Always 200 OK. Success and logical failures (including signature rejection) both return 200 with ok reflecting the outcome. |
contact.creation webhook | Retries on 5xx; one delivery per retryable failure. | Always 200 OK to discourage retries on per-event failures. Per-event errors are logged with status failed and returned in the body with ok: false + code. |
Workflow custom action (/send) | Retries on 5xx; the workflow execution itself stalls until the action returns. | Always 200 OK with outputFields.ok reflecting the actual outcome. The workflow author branches on ok / code, not on HTTP status. (Previously this route returned non-200 on some failures, which broke HubSpot’s contract — now fixed.) |
| Sidebar UI extension | No retry — the operator clicks again. | Always 200 OK with the ok flag plus code / message / retryable / trace_id on failure. |
From Chert to HubSpot
| Path | Retry behavior |
|---|---|
| Reply push | One inline retry on 401 after a forced token refresh. No further retries from this code path. The reply-notify cron re-runs every two minutes; transient HubSpot failures retry on the next tick. |
| Reply push circuit breaker | Five consecutive failures trip the breaker for the tenant. Subsequent pushes are skipped until a successful push resets the counter. |
| OAuth token refresh | One attempt per call site. A persistent refresh failure surfaces to the caller. |
Inside the Chert pipeline
| Path | Retry behavior |
|---|---|
| Outbound send dispatch | Handled by the Chert delivery pipeline, transparent to the integration. |
| Lead resolve | No retry. Failure is returned to the caller as a structured error. |
Idempotency
| Source | Key | Effect on retry |
|---|---|---|
| Workflow custom action | HubSpot’s callbackId | Same callback always returns the prior response. |
| Sidebar card send | widget:{tenantId}:{contactId}:{secondBucket} | Double-clicks within the same second collapse to one send; the next second is a fresh attempt. |
| Contact creation webhook | webhook:contact_creation:{objectId} | The same contact creation event never re-fires. |
| Chert internal pipeline | (phone_line, recipient, message) for six hours | Identical messages within six hours dedup at the dispatch layer. |
duplicate: true is a successful outcome and
should be treated identically to duplicate: false by downstream
workflow branches.
Sandbox versus Production
HubSpot’s developer sandbox accounts are first-class for this integration. The same install flow, scopes, and signature scheme apply. Differences to plan for:| Concern | Sandbox | Production |
|---|---|---|
| Rate limit | Lower than production | Edition-dependent; see HubSpot docs |
| Webhook delivery | Delayed under load | Real-time |
| Phone line | Use a Chert test project with test phone lines | Use the production project |
| Workflow execution | Manual triggering only | Triggered by real contact events |
See Also
- Architecture — where each limit applies in the call graph.
- Configuration — daily cap and sending window settings.
- Security — what is logged for retried events.

