How to Automate Weekly KPI Reports in Slack Without Publishing Bad Numbers

Schedule the weekly KPI report for Tuesday, not Monday morning, and let it refuse to publish when a required source did not return. Google says GA4 processing can take 24 to 48 hours, so Sunday's numbers can still change on Monday, and a bot that posts anyway presents a partial week as final. Give every metric a declared period, source and owner, compute each change in the source tool and let the agent only quote it, and post a blocked notice instead of a clean-looking summary when data is missing.
This is for an operations lead or analyst who reopens the same dashboards every week, aligns date ranges, copies figures and pastes a summary into Slack. Basic automation removes the copying and adds a harder failure: a scheduled bot can publish stale, partial or differently scoped numbers as if the report had succeeded.
Write down what the report may claim
A dashboard lets an analyst explore. A weekly report makes a dated claim to a team: this is what happened, what changed and where someone must act. Copying a chart into Slack hides the definitions and checks behind that claim, so start from the claim and not from a cron expression. Four failures recur when the claim is vague.
- Reporting windows drift. One source uses the previous calendar week, another the last seven complete days, a third includes today. Each number is right for its own period and the comparison is false.
- A nonempty response is mistaken for complete data. An API can return rows when a connector lost access to one account, a dimension changed or late events have not arrived.
- No activity is confused with a failed read. Zero incidents may be good news, and zero rows from an expired credential is not. A GitHub feature request for verified scheduled-report outcomes keeps four results apart: sources read, sources unavailable, no activity, and publication failed. It is one team's requirement rather than a platform guarantee, and the four-way split is worth copying.
- Nobody can regenerate Monday's message for the same period after a dispute. Another GitHub request, for aggregate Slack summaries, asks for explicit windows, filters, comparisons, scheduled delivery and manual backfills in the same format. It is written for one product, but the problem applies to any recurring report.
For every metric, record its owner, definition, source, segment, timezone, allowed lateness, comparison period and behavior when unavailable, and mark which metrics are required for publication. A secondary annotation can be omitted with a warning. A required revenue, support or reliability metric should block the report if its source cannot be verified. A compact contract can look like this:
report_id: weekly-operations-kpis
owner: business-operations
schedule_timezone: America/Los_Angeles
period:
kind: previous_complete_week
week_starts: monday
close_delay_hours: 48
metrics:
- id: organic_sessions
source: google_analytics
definition_version: v3
segment: channel_group=Organic Search
required: true
- id: nonbrand_clicks
source: search_console
definition_version: v2
search_type: web
required: true
- id: support_escalations
source: approved_support_export
definition_version: v1
required: false
publication:
destination: slack
channel_id: C012345
status_when_optional_source_fails: partial
status_when_required_source_fails: blocked
duplicate_key: weekly-operations-kpis:{period_start}:{period_end}The format is up to you; YAML is only an example. What matters is that scheduled runs and backfills use the same period, metric definition, source, and failure rule.
Version metric definitions. If marketing changes an organic-session filter or support changes what counts as an escalation, do not silently rewrite history. Record the new version and its effective date, and flag a report that spans the change instead of showing a smooth but misleading trend.
Freeze the period, then wait for the slowest source
At the start of the run, calculate one closed interval in the contract's timezone and store the start, end, timezone, schedule time and run identifier. Pass that frozen period to every connector, and never let individual tools interpret "last week" independently. If a source cannot represent the interval exactly, stop or label the mismatch before comparing values.
Then decide when the interval counts as closed. Google's help page on data freshness says GA4 data processing can take 24 to 48 hours and that report data may change during that time. A report posted Monday morning for a week that ended Sunday night can therefore be revised by Tuesday, and the Slack message will not be. Set the close delay to the processing time of your slowest source (the contract above waits 48 hours) and post on Tuesday, or label an earlier report preliminary on its first line. This is our recommendation for GA4-based reports, and it is worth checking every other source's freshness notes the same way.
Run it as a person, not a service account
Resolve the report owner and connected account before requesting data. The scheduled job should not become a privileged service identity that sees more than the owner can inspect.
The Kipwise Agent works in Slack threads, runs scheduled requests, uses connected Google access, and acts with the requesting user's permissions. It cannot read or edit a page the user cannot open. That gives operations leaders a useful boundary: scheduled access should match live-request access, and removing a person's access should remove the agent's access on that person's behalf.
For shared operational reports, assign a durable human owner and a backup. If the owner leaves or loses access, block the run with a clear ownership error. Do not fall back to an unrelated administrator credential.
Read each source and record what you read
Fetch each required source independently and keep the request metadata beside the result, so a value can be reproduced without storing unnecessary raw customer or employee data. Google's Analytics Data API overview documents programmatic reporting and methods including runReport and batchRunReports. It also states that returned data follows the reporting identity configured for the property. Record the property, method, dimensions, metrics, date range, filters, retrieval time, and row count. For a Search Console metric, keep the site, search type, dimensions, filters, period, and aggregation rule. For an Ads metric, keep the account label, currency, network, targeting, and whether the number is modeled. For a file input, keep the file identifier, checksum, version, and approved extraction rule.
End each source in one of four states: read, no_activity, unavailable, or invalid. Only read and a verified no_activity may contribute to a complete report, and a zero counts as verified only when the source answered successfully and returned the dimensions needed to tell no activity from missing data.
Validate before any narrative is written. Check that every required source returned for the declared period, that its newest data meets the freshness rule, that compared metrics use compatible segments, definitions, currency and timezone, that the data was read and not restored from an expired cache, and that the result sits within an expected range. A 70 percent drop may be a real event. Mark it anomalous and attach the source evidence instead of forcing the value back into a familiar range.
Let the agent quote numbers, not calculate them
Compute every change, percentage and total in the source tool or a spreadsheet, and have the agent only quote the result with its period and link. Then each figure in the Slack sentence traces to a cell or a query, and a wrong sentence points to a wrong input instead of an unexplained calculation. When a comparison denominator is zero, report the absolute change and omit the percentage.
Keep facts and interpretation apart. "Organic sessions decreased 18 percent week over week" is a calculated claim. "The decline came from a ranking update" is a causal claim that needs separate evidence, so the report should name who supplied the explanation or ask the owner for one rather than inventing it. The weekly update has four blocks: reporting status, KPI summary, exceptions, and owned next actions, with source failures at the top. A partial report must look partial.
Publish once, correct in the open
Create a duplicate key from the report identity and period, and check whether it already has a confirmed Slack message before posting, so a retry after a timeout reconciles the known run instead of posting a second official report. Slack's chat.postMessage documentation explains that thread_ts creates a reply to a parent message, recommends a top-level text fallback for accessibility when blocks are used, and documents channel permissions and posting limits. Use the parent message as the weekly record, put evidence, corrections and questions in its thread, and include readable top-level text instead of relying only on visual blocks.
If Slack returns an uncertain delivery result, reconcile by the duplicate key before retrying and record published, publication_failed, or publication_unknown. An unknown result is not a success because report generation finished.
A backfill calls the same contract with a historical period, not a separate analyst spreadsheet or different filters, and is labeled as a backfill and linked to the original report. If a published metric is wrong, post a correction in the same thread and mark the original claim as superseded, keeping the old value, new value, reason, approver and definition version. A changed metric definition needs approval from the named owner. The agent can draft the contract update and show the affected reports, but a calculation casually described in a thread must not take effect.
When a source fails
When a required source is unavailable, post a blocked-run notice instead of a KPI summary: the failed source, period, last successful read, error category, owner and next retry rule. Do not repeat a cached number without displaying its age and approval status. When an optional source fails, publish a partial report only if the contract allows it, remove the dependent calculations and keep the failure visible above the remaining metrics.
When a permission check fails, do not ask another connector for an equivalent-looking number. The difference between properties or accounts can be invisible in the final sentence, so route the access problem to the owner. If narrative generation fails after the metrics validate, keep the validated evidence and retry the narrative without refetching values. If Slack publication fails, retry delivery without rerunning the data requests unless the contract needs fresh data.
Break it on purpose before it posts
Test with synthetic or safely scoped data: an expired credential, an empty but valid period, a delayed analytics source, the wrong property, mixed timezones, a changed metric definition, a duplicate delivery retry, an inaccessible Slack channel, and a successful post with a failed status write-back. A run passes when each of those ends in a blocked notice or a labeled partial report and none ends in a report that looks complete. Then compare analyst minutes per report, missing-source failures caught before publication, duplicate posts, backfill agreement and corrections with several manual runs of the same report. Bot message count is not a success metric.
Start with one weekly operations report and three to five metrics. Write the contract, run the failure tests, and operate in draft-only mode for two cycles, with the operations owner comparing every generated value with its source. Enable scheduled Slack publication only after the missing-data, permission-loss, duplicate-delivery, backfill and correction tests all fail safely.

-p-1080.jpg)
-p-1080.jpeg)
