v5.2.1 - Webhook Triggers Receive the Full Parse Object
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/beforeSavehandlers 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 — soParse::Object#existed?always returnedfalseand#new?always returnedtrueinsideafterSave. Both are now reliable. - NEW:
afterSavehandlers on an updated object carry dirty tracking relative to the prior state, sotitle_changed?,changed, andchangeswork insideafterSavethe same way they already did insidebeforeSave. - 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 istrue/false, so forged_rperm/_wperm/authDatacannot be persisted through a handler. This applies only to the inbound trigger payload — client login/signup responses are unaffected and still return session tokens, andParse::Usercontinues to protectauthDataonpayload.user.
Lifecycle callbacks in ActiveModel order
- FIXED:
before_createcallbacks now run for objects created by any client (REST / JS cloud code / Auth0 / iOS). ThebeforeSavewebhook runsbefore_createafterbefore_savefor new objects; previously it never fired for non-Ruby creates, so create-time setup written asbefore_createwas silently skipped. - FIXED:
after_saveno longer double-fires on client-initiated saves. ThebeforeSaveentry point previously ran the full save callback chain, firingafter_saveduringbeforeSavein addition to theafterSavewebhook. It now runs the before phase only. - NEW:
Parse::Object#run_before_save_callbacksand#run_before_create_callbacks— the before-phase counterparts torun_after_save_callbacks/run_after_create_callbacks. They honor:if/:unlessconditions and the callback terminator. - CHANGED:
Parse::Object#prepare_save!is retained as a back-compat alias forrun_before_save_callbacksand now runs the before phase only.
Behavior Notes
- In an
afterSavehandler,new?now correctly returnsfalse(the object is persisted). Useexisted?to distinguish create from update insideafterSave(existed? == falsefor a create);new?is intended forbeforeSave. - Dirty-gated
after_saveside effects now fire on client/REST-initiated saves where they previously silently no-op'd. A callback such asafter_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 notbefore_update/after_update— those:update-specific callbacks fire only on Ruby-model saves, since Parse Server exposes nobeforeUpdate/afterUpdatetrigger. For update logic that must run for all clients, usebefore_save/after_saveand branch onexisted?.
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
endCommit: f3153d5
Author: Adrian Curtin
Date: June 5, 2026