integration scripts.md
Integration Scripts
Sync revenue totals to HubSpot CRM (read from Kick, write to HubSpot)
To push inflow totals by counterparty into HubSpot, aggregate transaction JSON from Kick, search for each company, then update it.
The Script
#!/bin/bash
# sync-revenue-to-hubspot.sh
WORKSPACE_ID="<workspace-id>"
HUBSPOT_API_KEY="your-hubspot-api-key"
SINCE="2026-01-01"
UNTIL="2026-01-31"
kick --workspace "$WORKSPACE_ID" transactions find \
--since "$SINCE" \
--until "$UNTIL" \
--fields amount,counterparty \
--output json | \
jq -c '
[.[] | select(.amount > 0)] |
group_by(.counterparty) |
map({client: .[0].counterparty, revenue: (map(.amount) | add)})
' | jq -c '.[]' | while IFS= read -r row; do
client=$(echo "$row" | jq -r '.client')
revenue=$(echo "$row" | jq -r '.revenue')
echo "Updating HubSpot for $client: $revenue"
company_id=$(curl -s -X POST "https://api.hubapi.com/crm/v3/objects/companies/search" \
-H "Authorization: Bearer $HUBSPOT_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"filterGroups\":[{\"filters\":[{\"propertyName\":\"name\",\"operator\":\"EQ\",\"value\":\"$client\"}]}]}" \
| jq -r '.results[0].id // empty')
if [ -z "$company_id" ]; then
echo "No HubSpot company found for $client"
continue
fi
curl -s -X PATCH "https://api.hubapi.com/crm/v3/objects/companies/${company_id}" \
-H "Authorization: Bearer $HUBSPOT_API_KEY" \
-H "Content-Type: application/json" \
-d "{\n \"properties\": {\n \"monthly_revenue\": \"$revenue\",\n \"last_revenue_update\": \"$(date -I)\"\n }\n }"
done
echo "HubSpot revenue sync finished"
How to Customize
- Match on a custom HubSpot property instead of
name - Add error handling and retry logic around the curl calls
- Filter inflows to specific categories in the
jqstep
What It Outputs
- Updates
monthly_revenueon matching HubSpot companies - Console output for each client processed
Export transactions to a data warehouse (read from Kick, write to database)
To load transaction rows into PostgreSQL, export JSON and stream CSV into COPY.
The Script
#!/bin/bash
# export-to-warehouse.sh
WORKSPACE_ID="<workspace-id>"
DB_HOST="your-db-host"
DB_NAME="analytics"
DB_USER="etl_user"
SINCE="2026-01-01"
kick --workspace "$WORKSPACE_ID" transactions find \
--since "$SINCE" \
--fields id,date,amount,counterparty,category \
--output json \
> /tmp/kick-transactions.json
jq -r '.[] | [.id, .date, .amount, .counterparty, (.category // "null")] | @csv' /tmp/kick-transactions.json | \
psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "
COPY transactions (id, date, amount, counterparty, category)
FROM STDIN WITH CSV
"
echo "Transactions loaded to warehouse"
How to Customize
- Track the last loaded date and pass it to
--sincefor incremental loads - Page with
--cursorwhen you exceed the default limit
What It Outputs
- Rows loaded into the
transactionstable - Console confirmation
Export project expenses to Notion (read from Kick, write to Notion)
To mirror expense rows into a Notion database, filter transactions by category in jq and create one page per row.
The Script
#!/bin/bash
# export-to-notion.sh
WORKSPACE_ID="<workspace-id>"
NOTION_API_KEY="your-notion-api-key"
DATABASE_ID="your-notion-database-id"
CATEGORY_NAME="Marketing"
SINCE="2026-01-01"
UNTIL="2026-03-31"
kick --workspace "$WORKSPACE_ID" transactions find \
--since "$SINCE" \
--until "$UNTIL" \
--fields date,amount,counterparty,category,memo \
--output json | \
jq -c --arg cat "$CATEGORY_NAME" '.[] | select(.amount < 0 and .category == $cat)' | \
while IFS= read -r txn; do
date=$(echo "$txn" | jq -r '.date')
amount=$(echo "$txn" | jq -r '.amount | fabs')
category=$(echo "$txn" | jq -r '.category')
counterparty=$(echo "$txn" | jq -r '.counterparty')
memo=$(echo "$txn" | jq -r '.memo // ""')
echo "Adding $counterparty ($amount) to Notion"
curl -s -X POST "https://api.notion.com/v1/pages" \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Content-Type: application/json" \
-H "Notion-Version: 2022-06-28" \
-d "{\n \"parent\": { \"database_id\": \"$DATABASE_ID\" },\n \"properties\": {\n \"Date\": { \"date\": { \"start\": \"$date\" } },\n \"Vendor\": { \"title\": [{ \"text\": { \"content\": \"$counterparty\" } }] },\n \"Category\": { \"select\": { \"name\": \"$category\" } },\n \"Amount\": { \"number\": $amount },\n \"Notes\": { \"rich_text\": [{ \"text\": { \"content\": \"$memo\" } }] }
}
}"
done
echo "Project expenses exported to Notion"
How to Customize
- Change
CATEGORY_NAMEor filter on counterparty instead - Map property names to match your Notion database schema
What It Outputs
- One Notion page per matching expense
- Console output for each row created
Send weekly expense summary to Slack (read from Kick, write to Slack)
To post last week's spend and top categories to a channel, aggregate transaction JSON and call a webhook.
The Script
#!/bin/bash
# weekly-expense-summary.sh
WORKSPACE_ID="<workspace-id>"
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
SINCE="$(date -v-7d +%Y-%m-%d 2>/dev/null || date -d '7 days ago' +%Y-%m-%d)"
UNTIL="$(date +%Y-%m-%d)"
expenses=$(kick --workspace "$WORKSPACE_ID" transactions find \
--since "$SINCE" \
--until "$UNTIL" \
--fields amount,category \
--output json \
| jq '[.[] | select(.amount < 0)]')
total=$(echo "$expenses" | jq '[.[].amount] | add | fabs')
top_categories=$(echo "$expenses" | jq -r '
group_by(.category) |
map({
category: (.[0].category // "Uncategorized"),
total: (map(.amount | fabs) | add)
}) |
sort_by(.total) | reverse | .[0:3] |
map("• \.category: $\.total") |
join("\n")
')
message="*Weekly expense summary*\n*$SINCE to $UNTIL*\n\n*Total expenses:* \
$*total\n\n*Top categories:*\n$top_categories\n\n<https://app.kick.co/transactions|View in Kick>"
curl -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"$message\"}"
echo "Weekly summary sent to Slack"
How to Customize
- Change the date window for daily or monthly summaries
- Add entity-scoped review in Kick before running the export
What It Outputs
Slack message with total expenses and top three categories.