« tlly.dev

Playbooks · Integrations

HubSpot

Open pipeline value and deal count from HubSpot CRM search. The search POST is allowed.

What you can track

Open deal value (sum of properties.amount) and open deal count (total on the search response), each as one point on the fetch day. Deals created, won and lost need a date property and aggregate. Companies and contacts use the same search path with their object name.

Credential

A HubSpot access token with only read scopes. Minimum for the pipeline jobs: crm.objects.deals.read. Add crm.objects.contacts.read and crm.objects.companies.read only when you chart those. No write scopes.

Host: api.hubapi.com. Suggested name: HUBSPOT_TOKEN. Header: Authorization: Bearer {{secret:HUBSPOT_TOKEN}}.

Where to get it

Newer accounts: Development → Keys → Service keys → Create service key. If Private apps says “Legacy apps aren't available on this account”, the service key is the path that works. Add the read scopes, Update, Create, Show, Copy. Service keys.

Older accounts: Settings → Integrations → Private apps (or Development → Legacy apps), app name “tlly”, the same read scopes. Private apps.

tlly job

POST exactly https://api.hubapi.com/crm/v3/objects/{deals,contacts,companies}/search with a JSON body is allowed with a stored secret. It copies paging.next.after into the body field after and stops when HubSpot omits it. If a cursor remains after max pages (at most 10), the job errors instead of saving a partial sum. Always set request.pages on a sum of results. If HubSpot returns paging.next.after and the job has no request.pages, the run fails and does not save the first page. Omitting extract.date on an aggregate snapshots every matching row as one point on the fetch day (a sum or a count, not the last row the way a date-less CSV works). properties.amount is a string and is summed. Filter open deals with hs_is_closed EQ false (a stage list misses custom closed stages). Search allows up to 200 objects per page.

Value:

{"series":"open_pipeline","cron":"0 7 * * *","request":{"url":"https://api.hubapi.com/crm/v3/objects/deals/search","method":"POST","headers":{"Authorization":"Bearer {{secret:HUBSPOT_TOKEN}}"},"body":{"filterGroups":[{"filters":[{"propertyName":"hs_is_closed","operator":"EQ","value":"false"}]}],"properties":["amount"],"limit":100},"pages":{"cursor":"paging.next.after","into":"after","max":10}},"extract":{"format":"json","items":"results","value":"properties.amount","aggregate":"sum"}}

Count (uses total, so it does not depend on paging):

{"series":"open_deals","cron":"0 7 * * *","request":{"url":"https://api.hubapi.com/crm/v3/objects/deals/search","method":"POST","headers":{"Authorization":"Bearer {{secret:HUBSPOT_TOKEN}}"},"body":{"filterGroups":[{"filters":[{"propertyName":"hs_is_closed","operator":"EQ","value":"false"}]}],"limit":1}},"extract":{"format":"json","value":"total"}}

Dashboard: panel 0 “Open deal value” on open_pipeline, unit USD, red_below 3000, then set_panel_alert on that panel with trigger red. Panel 1 “Open deals” on open_deals. Amounts are in the portal’s currency units, not cents (confirmed as a string field, not as a minor-unit rule).

Gotchas

Alert ideas

Red when open pipeline value drops below the cover target (the example uses 3000). Yellow when open deal count falls week over week.

Verification

Checked against the search docs and the server: POST /crm/v3/objects/{object}/search is on the secret-POST allowlist, and the job follows paging.next.after into after. Also checked: filterGroups, properties, limit, total, results[].properties, string amount, 200 per page. Service-key clicks (Development → Keys → Service keys, Show, Copy) follow the service-key docs. This playbook did not call live HubSpot.