How to get author/user info in GROQ-powered webhooks without transaction ID?

2 replies
Last updated: Sep 12, 2023
Hi,In the GROQ-powered Webhooks, I need to know who made the change in Sanity that triggered the webhook.

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
    ]
  }
}
Any way to know the author?

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.
AI Update

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 provider values 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 thread
2 replies
Can you try the identity() function? I believe that should work, even from within a webhook.
amazing, it worked beautifully! thank you Geoff!

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.

Was this answer helpful?