Which data issues your customers reported before you detected them
It is a Thursday incident review and there are two screens shared on the call.
On the first, the analytics engineering lead has Snowflake open. Query history filtered to the billing usage model. A dbt test failure at 04:12 on the 14th. A Time Travel query showing that the currency conversion column started returning nulls for EU accounts on the 5th, nine days before anything went red. Nine days of wrong euro totals sitting inside a customer-facing usage report.
On the second screen, the support manager has been scrolling Zendesk since the meeting started. There is a ticket from the 6th: "the euro totals on our usage page look wrong, is this a display bug?" One reply, solved same day. There is another from the 9th, different account, different agent: "invoice doesn't match what the dashboard says." Solved. A third on the 11th, escalated once, closed as a billing question.
Three customers saw it first. Nobody in the room knew that until somebody scrolled.
The question the review is really trying to ask is not about this incident. It is the general version: over the last two quarters, which data issues did customers report before we detected them, and how long did we sit on their report before we believed it. That number is the honest measure of whether your data quality monitoring is working. Both halves of the evidence exist. They live in two systems, and neither system can see the other half.
What Snowflake shows on its own
Snowflake is exact about its own state, and the surface area is genuinely large.
ACCOUNT_USAGE.QUERY_HISTORY tells you every statement that ran and when. COPY_HISTORY and TASK_HISTORY tell you which loads and which scheduled jobs failed, and with what error. Data metric functions give you freshness, null counts, duplicate counts and row-count deltas as scheduled measurements against a table. dbt test results land as a table you can query like any other. Time Travel lets you go back and ask what a column actually contained on the 5th, which is how you establish that the corruption predates the alert.
So Snowflake will tell you, with timestamps: this table went stale at 02:00, this test failed at 04:12, this column was already wrong nine days earlier, and here is the exact set of account IDs whose rows were affected.
Where it stops is a clean line. Snowflake knows what happened to the data. It has no record of what happened to the people looking at the data. It cannot tell you whether the wrong euro total ever reached a human being, whether that human noticed, or whether they wrote to you about it. And it only knows about checks you wrote in advance. The nine-day gap in this incident is not a Snowflake failure, it is the honest cost of nobody having written a null-rate test on that particular column until after the fact.
What Zendesk shows on its own
Zendesk holds the other half, and it holds it well for the job it was built for.
Every ticket has a requester, an organization, a created timestamp, a status history, tags, custom fields and a full comment thread. Views group the open work. Explore reports on volume, first reply time, resolution time, satisfaction, and any structured field you have defined. Search will look inside comment bodies, so if you already know the phrase to search for, you can find the tickets.
Where it stops is also a clean line, and it is not really Zendesk's fault. The diagnosis in a support ticket lives in the comment body, not in a field. Explore can group by tag, and it cannot group by "this customer is describing a symptom of a broken pipeline," because that is not a dimension, it is a reading. Tagging would fix it in theory and does not in practice: three different agents on three different days, working the queue at speed, each saw a one-off and closed it correctly as a one-off. There is no tag that unites "display bug," "invoice mismatch" and "billing question."
And Zendesk has no idea that a table exists. There is no timestamp in it that means "our test failed," so there is nothing in Zendesk to compare the ticket's created date against.
Why the gap is structural, not a missing feature
Look at what each half of the evidence physically is.
| Snowflake side | Zendesk side | |
|---|---|---|
| What the evidence is | A record: a test result, a load timestamp, a row value | A sentence somebody wrote, in their own words |
| Vocabulary | Your schema: table names, column names, account IDs | The customer's: "looks wrong," "doesn't match," "blank since Tuesday" |
| Describes | The cause | The symptom |
| Can you group by it | Yes, it is a column | No, it is a paragraph |
This is why a Snowflake Zendesk integration does not close the gap on its own. You can absolutely connect Snowflake and Zendesk today: Fivetran, Airbyte and similar tools will land Zendesk into Snowflake on a schedule, and plenty of teams do exactly that. What lands is the structured part. Ticket IDs, statuses, tags, timestamps, and often the comment text as a long string field nobody queries. You can now chart ticket volume next to test failures on one dashboard. You still cannot tell which tickets were about the failure.
The BI layer inherits the same limit for a structural reason. Tools like Looker's conversational analytics and Power BI Copilot are good at what they do, and what they do is reason over databases and modelled sources. Their whole competence assumes the evidence has been shaped into fields and measures first. Evidence that is a sentence somebody wrote is not badly modelled for them, it is outside what they can see at all.
Automation platforms sit out for a different structural reason. Zapier and Make are excellent, and they are trigger based: something happens, then records move forward. A Zap can open a Jira issue the moment a dbt test fails, which is a genuinely useful thing to have. But a trigger fires forward, and this question points backward, across two quarters of tickets that were already closed. There is no trigger for "what already happened."
What the join actually requires
Two things, and only one of them is mechanical.
The key is a time window plus a scope. For each incident you need the interval between when the bad data first entered the table, which Time Travel or the load history gives you, and when you detected it, which the test result gives you. Then you need the affected scope: the account IDs, region, currency or segment whose rows were wrong. On the Zendesk side, the requester's organization maps to those account IDs, assuming that mapping exists. Tickets from those organizations, created inside that window, are the candidate set. That part is a join, and a machine can do it.
The judgement call is whether a ticket is actually about the incident. "Euro totals look wrong," "invoice doesn't match the dashboard" and "the chart has been blank since Tuesday" share no tag, no keyword and no field. They may all be the same root cause. Meanwhile a ticket that literally says "your data is wrong" may turn out to be a permissions problem where the customer simply could not see their own rows. Keyword matching gets you both false positives and misses, in roughly equal measure. Somebody has to read the thread and decide.
There is a second judgement underneath it: what counts as "detected." The test failing at 04:12, the alert being seen by a human at 09:30, and the incident channel opening at 11:00 are three different timestamps, and on a bad week they are days apart. Which one you compare the ticket against changes the answer.
How you would answer it with Skopx
Skopx connects to Snowflake and to Zendesk, along with nearly 1,000 other tools, and answers questions across them in chat with citations back to the source. The reading part is the part that was missing, so the sentence you type is close to the sentence you would say out loud in the review.
For every dbt test failure on customer-facing tables since January, find the affected accounts and check whether anyone from those accounts opened a Zendesk ticket describing the same symptom before the failure was detected. List the incidents where the customer got there first, with the ticket and the quote.
What comes back is a list with the reasoning attached: the incident, the detection timestamp, the earliest matching ticket, the gap between them, and the customer's own sentence so you can judge the match yourself rather than trusting it.
Once you have asked it twice, you stop wanting to retype it. Describing that same request as a standing console in chat produces one, and Skopx builds internal apps from a sentence: a page with a row per incident, columns for detected-at, first customer report, days the customer was ahead of you, affected accounts, and the quoted line from the ticket. It reads live from both systems every time you open it. Alongside each row sits one action, a button that posts the incident summary to your data quality channel, which asks for confirmation before it does anything.
Honest limits
This does not monitor anything. It answers a question when you ask it, and it will not wake you up. Your alerting still belongs to Snowflake and whatever sits on top of it.
It cannot find issues nobody wrote about. Silent customers stay silent, and the majority of people who see a wrong number never file a ticket at all. Any number this produces is a floor, not a total.
It depends on ticket text being worth reading. Where support closes threads with "resolved" and nothing else, there is no sentence to read and nothing to match.
Attribution needs the organization to account mapping to exist. Without it, tickets can be matched by time but not by scope, which is much weaker evidence.
The reading is a judgement, so it will occasionally connect a ticket that does not belong. That is why the console shows the quote rather than just a count. A human rejects the bad matches in a few seconds, which is a very different job from scrolling Zendesk for twenty minutes during a meeting.
It reads with the credentials you connect and respects the permissions those credentials carry. It stores nothing, creates no records, and publishes nothing.
Team is $16 per seat per month with 2.3 million AI tokens included. Solo is $5. The console above costs a handful of those tokens each time somebody opens it.
Skopx Team
The Skopx engineering and product team