How to get author/user info in GROQ-powered webhooks without transaction ID?
Legacy webhook used to provide transaction id, from which I used to fetch transaction details which had author information.
The migration guide indicates that transaction id is not supported in the new groq powered webhooks.
https://www.sanity.io/help/6816a33e-f07d-4fdf-ad29-14c6337e19e3
// webhook projection
{
"transactionId": "Not supported",
"projectId": sanity::projectId(),
"dataset": sanity::dataset(),
"ids": {
"created": [
select(before() == null && after() != null => _id)
],
"deleted": [
select(before() != null && after() == null => _id)
],
"updated": [
select(before() != null && after() != null => _id)
],
"all": [
_id
]
}
}Purpose to know this is to identify which webhook trigger came from a change done by user on sanity studio, and which one came from change done by my own robot script. I need to differentiate for processing only user initiated changes, as my robot script has already done the changes needed and need to avoid calling it again.
Thanks.
I understand your challenge! You want to know who made the change that triggered your GROQ-powered webhook so you can differentiate between user-initiated changes and your robot script's changes.
The good news is there's a solution using the identity() function in your webhook projection, combined with the Users API to get full author details.
The Solution
1. Add identity() to your webhook projection:
{
"projectId": sanity::projectId(),
"dataset": sanity::dataset(),
"documentId": _id,
"revisionId": _rev,
"operation": delta::operation(),
"before": before(),
"after": after(),
"author": identity(), // This gives you the user/robot ID
"logTime": now()
}2. In your webhook handler, fetch user details:
const { author } = req.body;
const userResponse = await fetch(
`https://${projectId}.api.sanity.io/v2022-04-29/users/${author}`,
{
headers: {
Authorization: `Bearer ${sanityToken}`
}
}
);
const authorResult = await userResponse.json();
const { displayName, email, sanityUserId, provider } = authorResult;Differentiating Robot vs User
The key is that robot users and human users will have different characteristics in the response:
- Human users will have
providervalues like"google","github", etc. - Robot users (API tokens/service accounts) will have
provider: "sanity"and typically have"robot"in their naming or metadata
You can filter based on this:
const isRobotUser = authorResult.provider === 'sanity' ||
authorResult.email?.includes('robot') ||
authorResult.displayName?.toLowerCase().includes('robot');
if (isRobotUser) {
// Skip processing - this was your script
return res.status(200).send('Robot change - skipped');
}
// Process user-initiated changes
processUserChange(body);Alternative: Use Mutation Tags
Another approach is to tag your robot mutations explicitly when you create them:
client.mutate({
mutations: [...],
tag: 'robot-script' // Custom tag
})While mutation tags aren't directly exposed in GROQ webhook projections, you could potentially track these separately or use the identity-based approach above which is more reliable.
Pro tip: Cache the user lookup results to reduce API calls, since the same users will likely make multiple changes.
Here's a ready-to-use webhook template that includes the author information to get you started quickly.
Show original thread2 replies
Was this answer helpful?
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.