Class: Parse::Webhooks::Payload

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Serializers::JSON
Defined in:
lib/parse/webhooks/payload.rb

Overview

Represents the data structure that Parse server sends to a registered webhook. Parse Parse allows you to receive Cloud Code webhooks on your own hosted server. The Parse::Webhooks class is a lightweight Rack application that routes incoming Cloud Code webhook requests and payloads to locally registered handlers. The payloads are Payload type of objects that represent that data that Parse sends webhook handlers.

Constant Summary collapse

ATTRIBUTES =

The set of keys that can be contained in a Parse hash payload for a webhook.

{ master: nil, user: nil,
installationId: nil, params: nil,
functionName: nil, object: nil,
original: nil, update: nil,
query: nil, log: nil,
objects: nil,
triggerName: nil,
event: nil, clients: nil, subscriptions: nil,
context: nil }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash = {}, webhook_class = nil) ⇒ Payload

You would normally never create a Parse::Webhooks::Payload object since it is automatically provided to you when using Parse::Webhooks.

Parameters:

  • hash (String, Hash) (defaults to: {})

    the raw webhook body (JSON string or Hash).

  • webhook_class (String, nil) (defaults to: nil)

    the Parse class name derived from the webhook URL path (<endpoint>/<triggerName>/<className>). This is the ONLY authoritative source of the class for beforeFind/afterFind triggers — Parse Server omits className from the find payload body entirely (the matched objects carry no className and there is no top-level one). Threading it here lets parse_class resolve (so find triggers route) and lets :vector columns be stripped from afterFind objects. For save/delete triggers the path className equals the body's, so it is consistent; for functions it is nil.

See Also:



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/parse/webhooks/payload.rb', line 125

def initialize(hash = {}, webhook_class = nil)
  hash = JSON.parse(hash, max_nesting: 20) if hash.is_a?(String)
  hash = Hash[hash.map { |k, v| [k.to_s.underscore.to_sym, v] }]
  @raw = hash
  # Set BEFORE the vector scrub below so the route-derived class is
  # available to strip :vector columns from afterFind objects (whose
  # body carries no className of its own).
  @webhook_class = webhook_class.to_s if webhook_class && !webhook_class.to_s.empty?
  @master = hash[:master]
  # Capture the caller's session token from the *unscrubbed* user hash
  # before scrub_credentials strips it below. Parse Server includes
  # `user.sessionToken` on every trigger fired by a logged-in caller
  # (it is absent for master-key-originated requests). Pulling it aside
  # here -- rather than leaving it in @user -- keeps it out of any object
  # a handler might persist and out of #as_json / the request log, while
  # still letting a handler opt in to acting as the calling user via
  # #session_token / #user_client / #user_agent.
  @session_token = self.class.extract_session_token(hash[:user])
  # LiveQuery beforeConnect/beforeSubscribe carry the caller's session
  # token at the TOP LEVEL (not nested under `user`), because no user is
  # resolved yet when the trigger fires. Capture it here -- with the same
  # "set it aside, keep it out of as_json / the log" treatment as the
  # nested form -- so #user_client / #user_agent can act as the caller.
  # It is intentionally NOT one of ATTRIBUTES.
  if @session_token.nil?
    top_token = hash[:session_token].to_s.strip
    @session_token = top_token unless top_token.empty?
  end
  # Webhook trigger payloads (beforeSave/afterSave/etc.) are delivered by
  # Parse Server and, when a webhook key is configured (the default; see
  # Parse::Webhooks.allow_unauthenticated for the opt-out used in tests /
  # local dev), authenticated by it -- so they are treated as trusted,
  # server-authoritative state. A handler is meant to receive the full
  # object -- createdAt/updatedAt, ACL, internal fields and all. The only
  # thing stripped here is genuine credential material a handler never
  # legitimately needs to read inline (live session tokens -- captured
  # above for opt-in user-scoped clients first -- and offline-crackable
  # password hashes); see WEBHOOK_TRIGGER_CREDENTIAL_KEYS. Protection
  # against *persisting* forged privileged fields lives on the write path
  # (changes_payload emits only declared, dirty-tracked properties), not on
  # this read path.
  if hash[:user].present?
    # Trusted hydration via .build (not .new) so server-sent timestamps and
    # data fields remain readable; credentials are removed first. Note
    # Parse::User applies its own protections, so `payload.user.auth_data`
    # is not exposed here. The built object is pristine, so a handler that
    # saves payload.user transmits nothing (no dirty changes) and cannot
    # persist forgeries.
    @user = Parse::User.build(self.class.scrub_credentials(hash[:user]))
  end
  @installation_id = hash[:installation_id]
  @params = hash[:params]
  @params = @params.with_indifferent_access if @params.is_a?(Hash)
  @function_name = hash[:function_name]
  @trigger_name = hash[:trigger_name]
  # Resolve the model class once so :vector columns can be stripped from
  # every object-shaped payload (see scrub_vector_columns). Credentials
  # are scrubbed first, then vectors. The route-derived @webhook_class is
  # authoritative and preferred — it is the only class source for
  # afterFind (whose body carries no className anywhere); for save/delete
  # it equals the body's className. Falls back to the object/original
  # className for older callers that don't supply a route class.
  vec_klass = self.class.resolve_klass_by_name(@webhook_class) ||
              self.class.resolve_vector_klass(hash[:object], hash[:original])
  @object = self.class.scrub_vector_columns(self.class.scrub_credentials(hash[:object]), vec_klass)
  @original = self.class.scrub_vector_columns(self.class.scrub_credentials(hash[:original]), vec_klass)
  @update = self.class.scrub_vector_columns(self.class.scrub_credentials(hash[:update]), vec_klass) || {}
  # Added for beforeFind and afterFind triggers. afterFind objects are all
  # of one class but carry no className of their own, so the route-derived
  # vec_klass is the only way to strip their :vector columns.
  @query = hash[:query]
  # LiveQuery connection metadata. `event` is the afterEvent event type
  # (create/enter/update/leave/delete) or "connect" for beforeConnect;
  # `clients`/`subscriptions` are connection-global counts. All nil for
  # the object / auth triggers. These are plain scalars (no credential
  # material), so they pass through unscrubbed.
  @event = hash[:event]
  @clients = hash[:clients]
  @subscriptions = hash[:subscriptions]
  @objects = Array(hash[:objects]).map do |o|
    self.class.scrub_vector_columns(self.class.scrub_credentials(o), vec_klass)
  end
  @log = hash[:log]
  # Caller-supplied context object threaded via X-Parse-Cloud-Context.
  # This is caller metadata (not a credential), so it passes through
  # without scrubbing — mirroring the treatment of @query and @log.
  @context = hash[:context]
  # Blocks registered by a handler via #after_response / #defer, to run
  # after the webhook response has been sent (drained by the Rack app).
  @deferred_callbacks = []
end

Instance Attribute Details

#clientsInteger?

Connection-global metadata sent on the LiveQuery +beforeConnect+ / +afterEvent+ triggers: the number of currently-connected LiveQuery clients. +nil+ for non-LiveQuery triggers.

Returns:

  • (Integer, nil)


85
# File 'lib/parse/webhooks/payload.rb', line 85

attr_accessor :event, :clients, :subscriptions

#contextHash?

The caller-supplied context object threaded from the originating REST write or cloud-function call via the +X-Parse-Cloud-Context+ header. Parse Server includes this as a top-level +context+ key in trigger payloads (beforeSave/afterSave/etc.). Returns a Hash when present, or +nil+ when the originating request carried no context.

Returns:



93
94
95
# File 'lib/parse/webhooks/payload.rb', line 93

def context
  @context
end

#eventString?

The LiveQuery event type for an +afterEvent+ trigger -- one of +"create"+, +"enter"+, +"update"+, +"leave"+, or +"delete"+ -- or +"connect"+ for a +beforeConnect+ trigger. +nil+ for every non- LiveQuery trigger. See #after_event? / #before_connect?.

Returns:



85
86
87
# File 'lib/parse/webhooks/payload.rb', line 85

def event
  @event
end

#function_nameString Also known as: functionName

Returns the name of the function.

Returns:

  • (String)

    the name of the function.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#installation_idString Also known as: installationId

Returns The identifier of the device that submitted the request.

Returns:

  • (String)

    The identifier of the device that submitted the request.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#logObject

The query request in a beforeFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Query] The set of matching objects in an afterFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Object] Logging information if available. Available in Parse Server 2.3.1 or later. @return [Hash] the set of matching objects in an afterFind trigger.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#masterBoolean

Returns whether the master key was used for this request.

Returns:

  • (Boolean)

    whether the master key was used for this request.



66
67
68
# File 'lib/parse/webhooks/payload.rb', line 66

def master
  @master
end

#objectHash

In a beforeSave, this attribute is the final object that will be persisted.

Returns:

  • (Hash)

    the object hash related to a webhook trigger request.

See Also:



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#objectsObject

The query request in a beforeFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Query] The set of matching objects in an afterFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Object] Logging information if available. Available in Parse Server 2.3.1 or later. @return [Hash] the set of matching objects in an afterFind trigger.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#originalHash

In a beforeSave, for previously saved objects, this attribute is the Parse::Object that was previously in the persistent store.

Returns:

  • (Hash)

    the object hash related to a webhook trigger request.

See Also:



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#paramsHash

Returns The list of function arguments submitted for a function request.

Returns:

  • (Hash)

    The list of function arguments submitted for a function request.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#queryObject

The query request in a beforeFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Query] The set of matching objects in an afterFind trigger. Available in Parse Server 2.3.1 or later. @return [Parse::Object] Logging information if available. Available in Parse Server 2.3.1 or later. @return [Hash] the set of matching objects in an afterFind trigger.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#rawHash

Returns the raw payload from Parse server.

Returns:

  • (Hash)

    the raw payload from Parse server.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#session_tokenObject (readonly)

Returns the value of attribute session_token.



105
106
107
# File 'lib/parse/webhooks/payload.rb', line 105

def session_token
  @session_token
end

#subscriptionsInteger?

Connection-global metadata sent on the LiveQuery +beforeConnect+ / +afterEvent+ triggers: the number of active subscriptions. +nil+ for non-LiveQuery triggers.

Returns:

  • (Integer, nil)


85
# File 'lib/parse/webhooks/payload.rb', line 85

attr_accessor :event, :clients, :subscriptions

#trigger_nameString Also known as: triggerName

Returns the name of the trigger (ex. beforeSave, afterSave, etc.).

Returns:

  • (String)

    the name of the trigger (ex. beforeSave, afterSave, etc.)



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#updateHash

Returns the update payload in the request.

Returns:

  • (Hash)

    the update payload in the request.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

#userParse::User

Returns the user who performed this request or action.

Returns:

  • (Parse::User)

    the user who performed this request or action.



66
# File 'lib/parse/webhooks/payload.rb', line 66

attr_accessor :master, :user, :installation_id, :params, :function_name, :object, :trigger_name

Instance Method Details

#after_delete?Boolean

true if this is a afterDelete webhook trigger request.

Returns:

  • (Boolean)


449
450
451
# File 'lib/parse/webhooks/payload.rb', line 449

def after_delete?
  trigger? && @trigger_name.to_sym == :afterDelete
end

#after_event?Boolean

true if this is a LiveQuery afterEvent webhook trigger request. The event type (create/enter/update/leave/delete) is in #event.

Returns:

  • (Boolean)


507
508
509
# File 'lib/parse/webhooks/payload.rb', line 507

def after_event?
  trigger? && @trigger_name.to_sym == :afterEvent
end

#after_find?Boolean

true if this is a afterFind webhook trigger request.

Returns:

  • (Boolean)


459
460
461
# File 'lib/parse/webhooks/payload.rb', line 459

def after_find?
  trigger? && @trigger_name.to_sym == :afterFind
end

#after_login?Boolean

true if this is a afterLogin webhook trigger request.

Returns:

  • (Boolean)


475
476
477
# File 'lib/parse/webhooks/payload.rb', line 475

def after_login?
  trigger? && @trigger_name.to_sym == :afterLogin
end

#after_logout?Boolean

true if this is a afterLogout webhook trigger request. The logged-out session is carried as #object / #parse_object (a +_Session+).

Returns:

  • (Boolean)


481
482
483
# File 'lib/parse/webhooks/payload.rb', line 481

def after_logout?
  trigger? && @trigger_name.to_sym == :afterLogout
end

#after_response { ... } ⇒ Boolean Also known as: defer

Register a block to run after this webhook's response has been sent to Parse Server, off the client's critical path. Use it to do work that should not add latency to the save/function the client is waiting on — search indexing, cache warming, fan-out notifications.

The handler still returns its value synchronously (the response Parse Server acts on); the deferred block runs afterward. When the SDK is mounted under a server that supports rack.after_reply (Puma, Unicorn) the block runs once the response is flushed to the socket, on the same worker thread; otherwise it runs in a detached thread. Each block is isolated, so one raising neither affects the response nor the others.

Parse::Webhooks.route :after_save, :Post do post = parse_object after_response { SearchIndex.reindex(post.id) } post end

self inside the block is this payload (it closes over the handler's scope), so parse_object, params, etc. remain available. Note the block runs in-process and does not survive a worker restart — for work that must happen, hand it to a durable job queue instead. Deferred callbacks fire only when the payload is processed through the mounted Parse::Webhooks Rack app.

Yields:

  • the work to run after the response is sent.

Returns:

  • (Boolean)

    true if a block was registered.



715
716
717
718
719
720
# File 'lib/parse/webhooks/payload.rb', line 715

def after_response(&block)
  return false unless block_given?
  @deferred_callbacks ||= []
  @deferred_callbacks << block
  true
end

#after_save?Boolean

true if this is a afterSave webhook trigger request.

Returns:

  • (Boolean)


439
440
441
# File 'lib/parse/webhooks/payload.rb', line 439

def after_save?
  trigger? && @trigger_name.to_sym == :afterSave
end

#after_trigger?Boolean

true if this is a afterSave or afterDelete webhook trigger request.

Returns:

  • (Boolean)


429
430
431
# File 'lib/parse/webhooks/payload.rb', line 429

def after_trigger?
  after_save? || after_delete? || after_find?
end

#attributesATTRIBUTES

Returns:



319
320
321
# File 'lib/parse/webhooks/payload.rb', line 319

def attributes
  ATTRIBUTES
end

#auth_trigger?Boolean

true if this is one of the authentication-side triggers (beforeLogin / afterLogin / afterLogout / beforePasswordResetRequest). These carry a +_User+ / +_Session+ as #object but are NOT object save/delete triggers: no ActiveModel save/create/destroy callbacks run for them, and Parse Server ignores the response body (the only way to affect a +before*+ one is to deny it -- see the webhook router).

Returns:

  • (Boolean)


517
518
519
# File 'lib/parse/webhooks/payload.rb', line 517

def auth_trigger?
  before_login? || after_login? || after_logout? || before_password_reset_request?
end

#before_connect?Boolean

true if this is a LiveQuery beforeConnect webhook trigger request. Connection-global: carries no #object; the className is the +@Connect+ sentinel and the caller's token (if any) is in #session_token.

Returns:

  • (Boolean)


494
495
496
# File 'lib/parse/webhooks/payload.rb', line 494

def before_connect?
  trigger? && @trigger_name.to_sym == :beforeConnect
end

#before_delete?Boolean

true if this is a beforeDelete webhook trigger request.

Returns:

  • (Boolean)


444
445
446
# File 'lib/parse/webhooks/payload.rb', line 444

def before_delete?
  trigger? && @trigger_name.to_sym == :beforeDelete
end

#before_find?Boolean

true if this is a beforeFind webhook trigger request.

Returns:

  • (Boolean)


454
455
456
# File 'lib/parse/webhooks/payload.rb', line 454

def before_find?
  trigger? && @trigger_name.to_sym == :beforeFind
end

#before_login?Boolean

true if this is a beforeLogin webhook trigger request.

NOTE: a +beforeLogin+ payload carries the user being authenticated as #object / #parse_object (a +_User+), NOT as #user -- the caller is not yet authenticated when the trigger fires, so #user is +nil+. (By +afterLogin+ both are populated and equal.) Reach for #parse_object to inspect the logging-in user during +beforeLogin+.

Returns:

  • (Boolean)


470
471
472
# File 'lib/parse/webhooks/payload.rb', line 470

def before_login?
  trigger? && @trigger_name.to_sym == :beforeLogin
end

#before_password_reset_request?Boolean

true if this is a beforePasswordResetRequest webhook trigger request. The target user is carried as #object / #parse_object (a +_User+).

Returns:

  • (Boolean)


487
488
489
# File 'lib/parse/webhooks/payload.rb', line 487

def before_password_reset_request?
  trigger? && @trigger_name.to_sym == :beforePasswordResetRequest
end

#before_save?Boolean

true if this is a beforeSave webhook trigger request.

Returns:

  • (Boolean)


434
435
436
# File 'lib/parse/webhooks/payload.rb', line 434

def before_save?
  trigger? && @trigger_name.to_sym == :beforeSave
end

#before_subscribe?Boolean

true if this is a LiveQuery beforeSubscribe webhook trigger request. Shaped like beforeFind: carries a #query (see #parse_query) and the className comes from the request path, not the body.

Returns:

  • (Boolean)


501
502
503
# File 'lib/parse/webhooks/payload.rb', line 501

def before_subscribe?
  trigger? && @trigger_name.to_sym == :beforeSubscribe
end

#before_trigger?Boolean

true if this is a beforeSave or beforeDelete webhook trigger request.

Returns:

  • (Boolean)


424
425
426
# File 'lib/parse/webhooks/payload.rb', line 424

def before_trigger?
  before_save? || before_delete? || before_find?
end

#client_initiated?Boolean

Returns true if this webhook was triggered by a client request (JavaScript, iOS, Android, etc.) This is the inverse of ruby_initiated? and is useful for callback logic that should only run for client-initiated operations.

Returns:

  • (Boolean)

    true if the request originated from a client (not Ruby)



772
773
774
# File 'lib/parse/webhooks/payload.rb', line 772

def client_initiated?
  !ruby_initiated?
end

#error!(msg = "") ⇒ Parse::Webhooks::ResponseError

This method will intentionally raise a ResponseError with a specific message. When used inside of a registered cloud code webhook function or trigger, will halt processing and return the proper error response code back to the Parse server.

Parameters:

  • msg (String) (defaults to: "")

    the error message to send back.

Returns:

Raises:

  • Parse::Webhooks::ResponseError



684
685
686
# File 'lib/parse/webhooks/payload.rb', line 684

def error!(msg = "")
  raise Parse::Webhooks::ResponseError, msg
end

#function?Boolean

true if this is a webhook function request.

Returns:

  • (Boolean)


349
350
351
# File 'lib/parse/webhooks/payload.rb', line 349

def function?
  @function_name.present?
end

#inspectObject

Redacted inspection. The default Ruby #inspect would dump every ivar, including the captured @session_token and the pre-scrub @raw hash (which still holds the caller's sessionToken and any password hashes). That is exactly the surface an error reporter or a stray p payload hits, so show only non-sensitive routing fields and a boolean for the token's presence. Use #as_json / the individual accessors for the (already credential-scrubbed) object data.



330
331
332
333
334
335
# File 'lib/parse/webhooks/payload.rb', line 330

def inspect
  "#<#{self.class.name} trigger=#{@trigger_name.inspect} " \
  "function=#{@function_name.inspect} class=#{parse_class.inspect} " \
  "id=#{parse_id.inspect} master=#{@master ? true : false} " \
  "session_token=#{@session_token ? "[FILTERED]" : "nil"}>"
end

#live_query_trigger?Boolean

true if this is one of the LiveQuery triggers (beforeConnect / beforeSubscribe / afterEvent). Parse Server delivers these over an HTTP webhook only in a co-located single-process LiveQuery setup; +beforeConnect+ in particular carries a live client and is effectively in-process-only. See the webhooks guide.

Returns:

  • (Boolean)


526
527
528
# File 'lib/parse/webhooks/payload.rb', line 526

def live_query_trigger?
  before_connect? || before_subscribe? || after_event?
end

#master?Boolean

true if the master key was used for this request.

Returns:

  • (Boolean)


354
355
356
# File 'lib/parse/webhooks/payload.rb', line 354

def master?
  @master.present?
end

#object?Boolean

true if this request is a trigger that contains an object.

Returns:

  • (Boolean)


531
532
533
# File 'lib/parse/webhooks/payload.rb', line 531

def object?
  trigger? && @object.present?
end

#original_parse_objectParse::Object

Returns a Parse::Object from the original object.

Returns:



536
537
538
539
540
541
542
# File 'lib/parse/webhooks/payload.rb', line 536

def original_parse_object
  return nil unless @original.is_a?(Hash)
  # Always pass the trigger's expected class explicitly so the
  # className inside the payload cannot redirect this hydration to a
  # different class.
  Parse::Object.build(@original, parse_class)
end

#parse_classString

Returns the name of the Parse class for this request.

Returns:

  • (String)

    the name of the Parse class for this request.



404
405
406
407
408
# File 'lib/parse/webhooks/payload.rb', line 404

def parse_class
  return @webhook_class if @webhook_class.present?
  return nil unless @object.present?
  @object[Parse::Model::KEY_CLASS_NAME] || @object[:className]
end

#parse_idString Also known as: objectId

Returns the objectId in this request.

Returns:

  • (String)

    the objectId in this request.



411
412
413
414
# File 'lib/parse/webhooks/payload.rb', line 411

def parse_id
  return nil unless @object.present?
  @object[Parse::Model::OBJECT_ID] || @object[:objectId]
end

#parse_object(pristine = false) ⇒ Parse::Object

This method returns a Parse::Object by combining the original object, if was provided, with the final object. This will return a dirty tracked Parse::Object subclass, that will have information on which fields have changed between the previous state in the persistent store and the one about to be saved.

Parameters:

  • pristine (Boolean) (defaults to: false)

    whether the object should be returned without dirty tracking.

Returns:

  • (Parse::Object)

    a dirty tracked Parse::Object subclass instance



550
551
552
553
554
555
556
557
558
559
560
# File 'lib/parse/webhooks/payload.rb', line 550

def parse_object(pristine = false)
  return nil unless object?
  return Parse::Object.build(@object, parse_class) if pristine
  # Memoize so pre-block guard application and the user webhook handler
  # observe the same instance. Otherwise field_guards applied on the
  # framework's pre-built object would be invisible to the block's
  # later parse_object call (which would construct a fresh dirty-tracked
  # object from @object/@original).
  return @parse_object if defined?(@parse_object) && !@parse_object.nil?
  @parse_object = build_parse_object
end

#parse_queryParse::Query

Returns the Parse query for a beforeFind trigger.

Returns:

  • (Parse::Query)

    the Parse query for a beforeFind trigger.



732
733
734
735
# File 'lib/parse/webhooks/payload.rb', line 732

def parse_query
  return nil unless parse_class.present? && @query.is_a?(Hash)
  Parse::Query.new parse_class, @query
end

#ruby_initiated?Boolean

Returns true if this webhook was triggered by a Ruby Parse Stack request. This is determined by checking for the 'RB' prefix in the request ID header. This flag is useful for preventing callback loops and implementing intelligent callback handling based on the request origin.

Returns:

  • (Boolean)

    true if the request originated from Ruby Parse Stack



742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
# File 'lib/parse/webhooks/payload.rb', line 742

def ruby_initiated?
  @ruby_initiated ||= begin
      request_id = nil

      if @raw.respond_to?(:[])
        # Check for headers at the top level first
        request_id = @raw["x-parse-request-id"] || @raw["X-Parse-Request-Id"] ||
                     @raw[:x_parse_request_id] || @raw[:'X-Parse-Request-Id']

        # If not found at top level, check nested headers
        if request_id.nil?
          headers_sym = @raw[:headers] if @raw[:headers].is_a?(Hash)
          headers_str = @raw["headers"] if @raw["headers"].is_a?(Hash)

          if headers_sym
            request_id = headers_sym["x-parse-request-id"] || headers_sym["X-Parse-Request-Id"]
          elsif headers_str
            request_id = headers_str["x-parse-request-id"] || headers_str["X-Parse-Request-Id"]
          end
        end
      end

      request_id&.start_with?("_RB_") || false
    end
end

#session_token?Boolean

true if this payload carried a caller session token -- i.e. the originating request was made by a logged-in user rather than the master key, so #user_client / #user_agent can act as that user.

Returns:

  • (Boolean)


362
363
364
# File 'lib/parse/webhooks/payload.rb', line 362

def session_token?
  !@session_token.nil?
end

#trigger?Boolean

true if this is a webhook trigger request.

Returns:

  • (Boolean)


419
420
421
# File 'lib/parse/webhooks/payload.rb', line 419

def trigger?
  @trigger_name.present?
end

#user_agent(**opts) ⇒ Parse::Agent?

An opt-in, non-master Agent scoped to the webhook caller's session token. Because its client has no master key and it is built with a non-empty +session_token:+, the agent runs in CLIENT MODE: every tool/query routes through a path Parse Server (or the SDK's own ACL/CLP enforcement layer) authorizes as the calling user, with no master-key fallback to silently bypass row-level security. This is the handle to use when a handler should read or act strictly within the caller's permissions. Additional agent options (e.g. +permissions: :readwrite+) may be passed through.

Parameters:

Returns:

  • (Parse::Agent, nil)

    +nil+ when the payload carried no token.



392
393
394
395
396
397
398
399
400
401
# File 'lib/parse/webhooks/payload.rb', line 392

def user_agent(**opts)
  return nil if @session_token.nil?
  require_relative "../agent" unless defined?(Parse::Agent)
  # Strip the two identity kwargs from the passthrough: a Ruby double-splat
  # that repeats an explicit keyword WINS, so user_agent(client: master)
  # or user_agent(session_token: other) would otherwise silently defeat the
  # whole point (scoping to the caller). The scoping is non-negotiable here.
  opts = opts.except(:session_token, :client)
  Parse::Agent.new(session_token: @session_token, client: user_client, **opts)
end

#user_clientParse::Client?

An opt-in, user-scoped Client for acting on the server as the webhook's calling user. It mirrors the default client's connection settings (+server_url+, +application_id+, +api_key+) but carries NO master key and BINDS the caller's #session_token, so every request it makes -- with no further ceremony -- is authorized by Parse Server as that user: ACL, CLP and +protectedFields+ are all enforced. (A Parse.with_session block still overrides the bound token if you need to act as someone else within a call.) Memoized per payload, since each webhook delivery carries a distinct token.

Returns:

  • (Parse::Client, nil)

    +nil+ when the payload carried no token.



376
377
378
379
# File 'lib/parse/webhooks/payload.rb', line 376

def user_client
  return nil if @session_token.nil?
  @user_client ||= Parse::Client.client.become(@session_token)
end

#wlog(s) ⇒ Object

Method to print to standard that utilizes the an internal id to make it easier to trace incoming requests.



339
340
341
342
343
344
345
346
# File 'lib/parse/webhooks/payload.rb', line 339

def wlog(s)
  # generates a unique random number in order to be used in logging. This
  # is useful when debugging issues in production where one server instance
  # may be running multiple threads and you want to trace the incoming call.
  @rid ||= rand(999).to_s.rjust(3)
  puts "[> #{@rid}] #{s}"
  @rid
end