Skip to content

v5.2.1 - Webhook Triggers Receive the Full Parse Object

Choose a tag to compare

@AdrianCurtin AdrianCurtin released this 05 Jun 02:10
· 33 commits to main since this release

Patch Release

Webhook trigger handlers now receive the full, server-authoritative Parse object — restoring createdAt/updatedAt, ACL, and dirty tracking inside afterSave — and the model lifecycle callbacks run in canonical ActiveModel order across the beforeSave/afterSave triggers (Parse Server has no separate create/update triggers). Only genuine credentials are stripped from the inbound payload.

Changes

Full object in webhook triggers

  • FIXED: afterSave/beforeSave handlers now receive the full object as Parse Server sends it (createdAt, updatedAt, ACL, internal fields). Previously the trigger payload was filtered through the wide mass-assignment denylist, which stripped the server-issued timestamps — so Parse::Object#existed? always returned false and #new? always returned true inside afterSave. Both are now reliable.
  • NEW: afterSave handlers on an updated object carry dirty tracking relative to the prior state, so title_changed?, changed, and changes work inside afterSave the same way they already did inside beforeSave.
  • CHANGED: Inbound webhook trigger payloads are now scrubbed of genuine credential material only (sessionToken, _hashed_password, _password_history) rather than the full mass-assignment denylist. Protection against persisting forged privileged fields stays on the write path: a save emits only declared, dirty-tracked properties, and an after-trigger response is true/false, so forged _rperm/_wperm/authData cannot be persisted through a handler. This applies only to the inbound trigger payload — client login/signup responses are unaffected and still return session tokens, and Parse::User continues to protect authData on payload.user.

Lifecycle callbacks in ActiveModel order

  • FIXED: before_create callbacks now run for objects created by any client (REST / JS cloud code / Auth0 / iOS). The beforeSave webhook runs before_create after before_save for new objects; previously it never fired for non-Ruby creates, so create-time setup written as before_create was silently skipped.
  • FIXED: after_save no longer double-fires on client-initiated saves. The beforeSave entry point previously ran the full save callback chain, firing after_save during beforeSave in addition to the afterSave webhook. It now runs the before phase only.
  • NEW: Parse::Object#run_before_save_callbacks and #run_before_create_callbacks — the before-phase counterparts to run_after_save_callbacks / run_after_create_callbacks. They honor :if/:unless conditions and the callback terminator.
  • CHANGED: Parse::Object#prepare_save! is retained as a back-compat alias for run_before_save_callbacks and now runs the before phase only.

Behavior Notes

  • In an afterSave handler, new? now correctly returns false (the object is persisted). Use existed? to distinguish create from update inside afterSave (existed? == false for a create); new? is intended for beforeSave.
  • Dirty-gated after_save side effects now fire on client/REST-initiated saves where they previously silently no-op'd. A callback such as after_save { notify if title_changed? } will now activate for objects created or updated via REST / JS cloud code, not only for Ruby-model saves.
  • The webhook layer runs before_save / before_create / after_create / after_save, but not before_update / after_update — those :update-specific callbacks fire only on Ruby-model saves, since Parse Server exposes no beforeUpdate / afterUpdate trigger. For update logic that must run for all clients, use before_save / after_save and branch on existed?.

Code Example

Parse::Webhooks.route :after_save, "Post" do
  post = parse_object

  if post.existed?            # reliable: false on create, true on update
    Search.reindex(post) if post.title_changed?
  else
    post.create_default_associations!
  end
  true
end

Commit: f3153d5
Author: Adrian Curtin
Date: June 5, 2026