How can I restrict backward pipeline stage movement in HubSpot?

I want to edit pipeline rules for a specific object where only specific users can move the stage backwards. Is there a custom rule that I can use. When I try the API call “GET /crm/pipelines-rules/2026-09-beta/{objectTypeId}/{pipelineId}” I receive an error or not found. How can I get the current rules of the pipeline and how can I set that only specific users can move the stages backwards.

Hey @ZHey and welcome back to the HubSpot Community!

Thanks for posting here!

You can use the Pipeline Rules API beta endpoint:

/crm/pipelines-rules/2026-09-beta/{objectTypeId}/{pipelineId}

The GET request requires the OAuth scope documented for the Pipeline Rules API. If the request returns a 404 or authorization error, verify that the private app or OAuth access token includes the required scope and that the objectTypeId and pipelineId are correct.

Regarding preventing backward movement, you could configure the noBackwardsMovementRule in governanceValidationRules:

{
“governanceValidationRules”: {
“noBackwardsMovementRule”: {
“enabledForAllStages”: true,
“pipelineStageIds”:
}
}
}

You’d need to set enabledForAllStages to true to prevent backward movement from every stage. To apply the rule only to selected stages, set it to false and provide those stage IDs in pipelineStageIds.

The Pipeline Rules API also provides separate stage-edit-permission endpoints:

GET /crm/pipelines-rules/2026-09-beta/{objectTypeId}/{pipelineId}/stage-edit-permissions
PUT /crm/pipelines-rules/2026-09-beta/{objectTypeId}/{pipelineId}/stage-edit-permissions

The PUT request accepts a stages array. Each stage can use:

  • permissionMode: OPEN or RESTRICTED

  • userIds: users allowed to edit records in the stage

  • teamIds: teams allowed to edit records in the stage

  • superAdmin: whether Super Admins should be included

  • pipelineStageId: the stage being configured

Example:

{
  "stages": [
    {
      "pipelineStageId": "stage_456",
      "permissionMode": "RESTRICTED",
      "userIds": ["12345"],
      "teamIds": [],
      "superAdmin": true
    }
  ]
}

Here is the Pipeline Rules API documentation for the current endpoint details, request schemas, and scope requirements.

Also, I’d love to hear from our Top Experts: Hi @Anton, @evaldas and @andrus do you have additional insights to share, please?

Thanks so much and have a wonderful day!

Bérangère

This post was created with the assistance of AI tools

Thank you very much for the response. Please can I also clarify with setting only specific users to allow backward movement. What happens if I define the specific users allowed to move backward, what are they able to do? And what happens to the other users not specified, what will they be able to do or not do?

To make sure I understand stage-edit-permissions correctly:

If I set permissionMode: RESTRICTED on a stage with userIds: ["12345"], can you confirm:

  1. Do the listed users (12345) get full edit access to that stage’s records (i.e., can edit any property, including moving forward or backward), or is their access limited to just the stage/backward-movement field specifically?
  2. Do non-listed users lose all edit access to records in that stage (every property), or only the ability to change the stage field?

What I’m actually trying to achieve is: specific users can move a record backward through stages, while all other users can still edit every other property and move the record forward normally just not backward. Is that level of granularity (specific users + specific direction, while leaving other edits open for everyone) possible with the current Pipeline Rules API, or is that outside what stage-edit-permissions and governanceValidationRules currently support?

If this isn’t natively supported, what would you recommend as the best workaround to achieve this?

Hi @ZHey and thanks for getting back to us!

Here is how you could proceed:

Please don’t enable noBackwardsMovementRule.

Instead, you could use a HubSpot Workflow with a stage-change trigger: when a record moves backward, check the initiating user against an allowlist (custom property or external webhook).

If the user isn’t allowed, auto-revert the stage to its previous value using a “Set property” action: CRM API | Pipelines - HubSpot docs

Alternatively, build a custom CRM extension or webhook-based validation that intercepts stage changes via the CRM API and rejects unauthorized backward moves before they commit.

The Workflow approach is the most practical no-code path; the webhook/extension approach gives you real-time enforcement but requires custom development.

I hope this helps!

Bérangère

This post was created with the assistance of AI tools

Thank you, this is very helpful. Could I ask for a bit more detail on each path? On the Workflow approach:
Standard workflow triggers/filters don’t seem to expose “which user made this property change” as a native enrollment trigger or filter option. How can I go about with this?

On the Webhook/CRM extension approach:
Could you point me to the right starting doc for this specific use case? A few things I’m trying to confirm:

  1. Do I need to register a public app in a developer account to use Webhooks v3, or can a private app also subscribe to property change events?
  2. Once subscribed to a dealstage/pipelinestage propertyChange event, the payload doesn’t appear to include the user who made the change, is Property History API the right place to look up who triggered it, or is there a more direct way?
  3. Is stage-change validation genuinely intercepted before the record saves, or is this always a “change happens, then gets reverted via API” pattern? I want to set the right expectation for end users about a possible brief flicker in the UI.

Hi @ZHey, thanks for the detailed follow-up! Happy to clarify each path.

Regarding the workflow approach: Identifying the user who made the change

You’re right that standard workflow triggers and filters don’t expose “which user made this property change” as a native enrollment option. Workfires fire on the property change event itself, not on the actor.

Here is a workaround with custom code action + property history API:

You can add a “Run code in HubSpot” custom code action to the workflow. This action receives the enrolled record (object ID, property values) but does not receive the identity of the user who triggered the change. Your code must explicitly call the CRM API with propertiesWithHistory to retrieve the most recent property value change entry, which includes sourceId (format: userId:12345 for direct user changes) and optionally updatedByUserId.

Two points to note:

  • updatedByUserId is optional, it may be absent for changes made by integrations, imports, workflows, or other system processes. When it’s missing, treat it as “system/unknown” rather than assuming any particular user.
  • This is a post-commit revert pattern. The workflow fires after the change has been saved, so there will be a brief window (seconds to minutes depending on workflow execution latency) where the stage has visibly changed before the revert action runs.

For the webhook / CRM extension approach:

1. Public app vs. private app for Webhooks v3

A private app can subscribe to webhook events for its single portal, you do not need a public app for a single-portal implementation. A public app (OAuth) is only required if you plan to distribute the integration across multiple portals.

To get started:

  • Create a private app in the target portal with the appropriate CRM object read scope
  • Configure a webhook subscription for deal.propertyChange on dealstage
  • Verify the exact property name, the pipeline stage property is generally dealstage for deals

Here is the documentation:

2. Does the webhook payload include the user who made the change?

No, the Webhooks v3 property change payload includes the object ID, property name, and the new/old value, but does not include the user who made the change.

To identify the user, call the Property History API (or the CRM Objects API with propertiesWithHistory) after receiving the webhook. The property value history entries include sourceId (which contains userId:<id> for direct user changes) and optionally updatedByUserId.

Here is the documentation for this: Get Property History: the sourceId field on historical values identifies the user (format: userId:12345)

Again, updatedByUserId is optional and may be absent for non-user writes (integrations, imports, workflows). Treat missing values as “system/integration/unknown.”

3. Pre-commit interception vs. post-commit revert

At the moment, there is no public API mechanism to intercept and block a stage change before it commits. The pattern is always:

Change happens → webhook fires → your service checks the user → if unauthorized, revert via API

If that’s something that would help you, I’d recommend sharing this via the HubSpot Developer Feedback form.

Also, I’d love to hear from our Top Experts: Hi @Alex_Boissonneault, @SteveHTM and @zach_threadint do you have any insights to share, please?

Thanks so much and have a fantastic day!

Bérangère

This post was created with the assistance of AI tools

Thanks for looping me in @Berangere, Hey @ZHey quick thoughts because i think you can avoid most of the code.

First the 404. That endpoint is real, but it’s a beta. Two things usually cause “not found” there: your private app is missing the crm.pipelines.governance.read scope (HubSpot returns 404 rather than 403 on some of these, which is confusing), or you’re passing the pipeline label instead of the pipeline ID. Grab the ID from GET /crm/v3/pipelines/deals first and try again. Also double check the objectTypeId, 0-3 for deals, 0-5 for tickets, and the 2-xxxx format for custom objects.

Second, and more importantly: there’s no rule anywhere, UI or API, that says “backward movement allowed for these users only”. The backward-movement rule is all-or-nothing, and super admins and anyone with “Edit property settings” bypass it anyway.

What i do with clients instead is combine two native rules. Turn on restrict backward movement for the stages that matter, then use the stage edit permissions rule (“Control object editing access”) to lock the later stages to a specific team. Result: reps can’t drag deals back, but the ops team you name can still edit those records. Not a perfect match for your ask, but it covers 90% of real cases without a webhook service to maintain.

If you truly need “this user may move backward, that one may not” on the same stage, then yes, @Berangere’s webhook + property history + revert pattern is the only way today. Just be honest with stakeholders that it’s a revert, not a block. Users will see the stage change and then see it snap back, and you’ll want a note in the activity timeline explaining why or your support inbox fills up.

Hope it helps!

Thank you for all the detail so far @Berangere and @Alex_Boissonneault , two more things I want to confirm before I implement this:

1. Scope of stage-edit-permissions
When permissionMode is set to RESTRICTED on a stage with specific userIds/teamIds, does that block non-listed users from editing every property on the record (name, amount, notes, custom fields), or only the pipeline/stage property specifically? I want to confirm whether this is a full record-edit lock or a narrower restriction, since the API doc describes it as “object editing access” but doesn’t spell out whether that’s all-fields or stage-only.

2. Closing the “Edit property settings” bypass on the backward-movement rule
Is there a way (either in the UI or via API) to prevent users with the “Edit property settings” permission from bypassing noBackwardsMovementRule? Specifically:

  • Is this bypass tied only to that one named permission toggle (Settings → Users & Teams → Permissions → CRM → [object] → Edit property settings), or are there other permissions that also grant this override?
  • Is there any account-level setting, beta flag, or upcoming feature that removes this bypass entirely for non-admin users, short of stripping that permission from everyone who currently has it for other legitimate reasons?

@ZHey, these are exactly the two things people trip on.

1. Stage edit permissions = full record lock, not stage-only.
When a stage is RESTRICTED and you’re not in the userIds/teamIds list, the whole record goes read-only for you while it sits in that stage. Name, amount, custom fields, the stage itself, all of it. The only things you can still do are engagement-type stuff: log a note, call, email, task, because those are separate objects, not deal properties. So treat it as “who can touch this record at all”, not “who can change the stage”. Test it in a sandbox with a non-admin login before rollout, the record just shows a lock and greyed fields, no warning up front.

Small side note on your 404: the docs have moved from 2026-09-beta to plain 2026-09 in the path, so try GET /crm/pipelines-rules/2026-09/0-3/{pipelineId} and the /stage-edit-permissions sub-path for the per-stage rules. Scope-wise you need crm.pipelines.governance.read for the validation rules and crm.pipelines.stage_permissions.read/write for the edit permissions, they’re separate scopes.

2. The bypass can’t be closed, and it’s wider than one toggle.
Per the KB, everything except deal approvals is bypassed by: Super Admins, anyone with “Edit property settings” (Settings > Users & Teams > CRM tab), workflows, and API writes. No account setting, no beta flag, nothing on the roadmap I’ve seen that removes it. HubSpot’s stance is basically “these are guardrails, not security”. Two practical takeaways:

  • Audit who actually has Edit property settings. In most portals it’s 5 to 10 people who got it once to make a dropdown, and it can come off without breaking anything. That’s the real fix.
  • Because API writes bypass the rules too, any integration that syncs dealstage (Salesforce, Zapier, your own scripts) will quietly walk straight through both rules. Worth knowing before you blame a user.

If you truly need a hard gate nobody can skip, deal approvals is the only rule that can’t be bypassed, but it fires on entering a stage, so it’s a blunt instrument for a “backwards” problem. For the specific-user-can-go-back case, Berangere’s webhook revert is still the only path.

Hope it helps!