Skip to content

Commit 24217a4

Browse files
CopilotGrantBirki
andcommitted
Update handler plugin docs to reflect JSON-first default behavior
Co-authored-by: GrantBirki <[email protected]>
1 parent eaaf3a5 commit 24217a4

File tree

1 file changed

+57
-9
lines changed

1 file changed

+57
-9
lines changed

docs/handler_plugins.md

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,33 @@ This document provides in-depth information about handler plugins and how you ca
44

55
## Writing a Handler Plugin
66

7-
Handler plugins are Ruby classes that extend the `Hooks::Plugins::Handlers::Base` class. They are used to process webhook payloads and can do anything you want. They follow a very simple interface that allows you to define a `call` method that takes three parameters: `payload`, `headers`, and `config`. The `call` method should return a hash with the response data. The hash that this method returns will be sent back to the webhook sender as a JSON response.
7+
Handler plugins are Ruby classes that extend the `Hooks::Plugins::Handlers::Base` class. They are used to process webhook payloads and can do anything you want. They follow a very simple interface that allows you to define a `call` method that takes four parameters: `payload`, `headers`, `env`, and `config`.
8+
9+
**Important:** The `call` method should return a hash by default. Since the server now defaults to JSON format, any hash returned by the handler will be automatically converted to JSON with the correct `Content-Type: application/json` headers set by Grape. This ensures consistent API responses and proper JSON serialization.
810

911
- `payload`: The webhook payload, which can be a Hash or a String. This is the data that the webhook sender sends to your endpoint.
1012
- `headers`: A Hash of HTTP headers that were sent with the webhook request.
1113
- `env`: A modified Rack environment that contains a lot of context about the request. This includes information about the request method, path, query parameters, and more. See [`rack_env_builder.rb`](../lib/hooks/app/rack_env_builder.rb) for the complete list of available keys.
1214
- `config`: A Hash containing the endpoint configuration. This can include any additional settings or parameters that you want to use in your handler. Most of the time, this won't be used but sometimes endpoint configs add `opts` that can be useful for the handler.
1315

16+
The method should return a **hash** that will be automatically serialized to JSON format with appropriate headers. The server defaults to JSON format for both input and output processing.
17+
1418
```ruby
1519
# example file path: plugins/handlers/example.rb
1620
class Example < Hooks::Plugins::Handlers::Base
1721
# Process a webhook payload
1822
#
1923
# @param payload [Hash, String] webhook payload (pure JSON with string keys)
2024
# @param headers [Hash] HTTP headers (string keys, optionally normalized - default is normalized)
21-
# @param env [Hash] A modifed Rack environment that contains a lot of context about the request
25+
# @param env [Hash] A modified Rack environment that contains a lot of context about the request
2226
# @param config [Hash] Endpoint configuration
23-
# @return [Hash] Response data
27+
# @return [Hash] Response data (automatically converted to JSON)
2428
def call(payload:, headers:, env:, config:)
29+
# Return a hash - it will be automatically converted to JSON with proper headers
2530
return {
26-
status: "success"
31+
status: "success",
32+
message: "webhook processed successfully",
33+
timestamp: Time.now.iso8601
2734
}
2835
end
2936
end
@@ -43,6 +50,30 @@ It should be noted that the `handler:` key in the endpoint configuration file sh
4350
- `MyCustomHandler` -> `my_custom_handler`
4451
- `Cool2Handler` -> `cool_2_handler`
4552

53+
## Default JSON Format
54+
55+
By default, the Hooks server uses JSON format for both input and output processing. This means:
56+
57+
- **Input**: Webhook payloads are parsed as JSON and passed to handlers as Ruby hashes
58+
- **Output**: Handler return values (hashes) are automatically converted to JSON responses with `Content-Type: application/json` headers
59+
- **Error Responses**: Authentication failures and handler errors return structured JSON responses
60+
61+
**Best Practice**: Always return a hash from your handler's `call` method. The hash will be automatically serialized to JSON and sent to the webhook sender with proper headers. This ensures consistent API responses and proper JSON formatting.
62+
63+
Example response format:
64+
```json
65+
{
66+
"status": "success",
67+
"message": "webhook processed successfully",
68+
"data": {
69+
"processed_at": "2023-10-01T12:34:56Z",
70+
"items_processed": 5
71+
}
72+
}
73+
```
74+
75+
> **Note**: The JSON format behavior can be configured using the `format` and `default_format` options in your global configuration. See the [Configuration documentation](./configuration.md) for more details.
76+
4677
### `payload` Parameter
4778

4879
The `payload` parameter can be a Hash or a String. If the payload is a String, it will be parsed as JSON. If it is a Hash, it will be passed directly to the handler. The payload can contain any data that the webhook sender wants to send.
@@ -159,6 +190,8 @@ The `log.debug`, `log.info`, `log.warn`, and `log.error` methods are available i
159190

160191
All handler plugins have access to the `error!` method, which is used to raise an error with a specific message and HTTP status code. This is useful for returning error responses to the webhook sender.
161192

193+
When using `error!` with the default JSON format, both hash and string responses are handled appropriately:
194+
162195
```ruby
163196
class Example < Hooks::Plugins::Handlers::Base
164197
# Example webhook handler
@@ -167,11 +200,12 @@ class Example < Hooks::Plugins::Handlers::Base
167200
# @param headers [Hash<String, String>] HTTP headers
168201
# @param env [Hash] A modified Rack environment that contains a lot of context about the request
169202
# @param config [Hash] Endpoint configuration
170-
# @return [Hash] Response data
203+
# @return [Hash] Response data (automatically converted to JSON)
171204
def call(payload:, headers:, env:, config:)
172205
173206
if payload.nil? || payload.empty?
174207
log.error("Payload is empty or nil")
208+
# String errors are JSON-encoded with default format
175209
error!("Payload cannot be empty or nil", 400)
176210
end
177211
@@ -182,21 +216,22 @@ class Example < Hooks::Plugins::Handlers::Base
182216
end
183217
```
184218

185-
You can also use the `error!` method to return a JSON response as well:
219+
**Recommended approach**: Use hash-based error responses for consistent JSON structure:
186220

187221
```ruby
188222
class Example < Hooks::Plugins::Handlers::Base
189223
def call(payload:, headers:, env:, config:)
190224
191225
if payload.nil? || payload.empty?
192226
log.error("Payload is empty or nil")
227+
# Hash errors are automatically converted to JSON
193228
error!({
194229
error: "payload_empty",
195230
message: "the payload cannot be empty or nil",
196231
success: false,
197232
custom_value: "some_custom_value",
198233
request_id: env["hooks.request_id"]
199-
}, 500)
234+
}, 400)
200235
end
201236
202237
return {
@@ -206,6 +241,17 @@ class Example < Hooks::Plugins::Handlers::Base
206241
end
207242
```
208243

244+
This will return a properly formatted JSON error response:
245+
```json
246+
{
247+
"error": "payload_empty",
248+
"message": "the payload cannot be empty or nil",
249+
"success": false,
250+
"custom_value": "some_custom_value",
251+
"request_id": "uuid-here"
252+
}
253+
```
254+
209255
### `#Retryable.with_context(:default)`
210256

211257
This method uses a default `Retryable` context to handle retries. It is used to wrap the execution of a block of code that may need to be retried in case of failure.
@@ -220,7 +266,7 @@ class Example < Hooks::Plugins::Handlers::Base
220266
# @param headers [Hash<String, String>] HTTP headers
221267
# @param env [Hash] A modified Rack environment that contains a lot of context about the request
222268
# @param config [Hash] Endpoint configuration
223-
# @return [Hash] Response data
269+
# @return [Hash] Response data (automatically converted to JSON)
224270
def call(payload:, headers:, env:, config:)
225271
result = Retryable.with_context(:default) do
226272
some_operation_that_might_fail()
@@ -229,7 +275,9 @@ class Example < Hooks::Plugins::Handlers::Base
229275
log.debug("operation result: #{result}")
230276
231277
return {
232-
status: "success"
278+
status: "success",
279+
operation_result: result,
280+
processed_at: Time.now.iso8601
233281
}
234282
end
235283
end

0 commit comments

Comments
 (0)