Package: servo-webhooks
servo-webhooksAn OpsaniServoconnector that provides a flexible webhooks
emitter based onservo events.The webhooks connector extends the eventing infrastruture provided by the servo to enable events
to be dispatched via HTTP or HTTP/2 request callbacks. Requests are delivered asynchronously on a
best effort basis. Webhooks can be registered to executebeforeorafterany event defined in
the servo assembly. Before event webhooks should be used with care as they can block execution of the
event pending delivery of the webhook or cancel the event entirely through the response (see below).
Support is provided for configurable automatic retry and timeout of webhook requests.Webhook requests are sent with the HTTPPOSTmethod and a JSON request body. The webhook request body
is dynamically defined based on the parameters and return value of the event registered with the servo.
This mechanism generalizes the webhook connector to support arbitrary events defined by any connector
within the servo assembly. TheContent-Typeheader and request body JSON Schema can be obtained
via thewebhooksCLI subcommand (see usage below).Configurationwebhooks:-name:my_measure_handler# Optional. Name of the webhook.description:Store measurement info into Elastic Search.# Optional: Textual description of the webhookevents:-after:measure# Required. Format: `(before|after):[EVENT_NAME]`url:https://example.com/webhooks# Required. Format: [URL]secret:s3cr3t# Required. Secret value for computing webhook signaturesheaders:# Optional, Dict[str, str]-name:x-some-headervalue:some valuebackoff:# Optional. Setting to `false` disables retries.max_tries:3max_time:5mA starting point configuration can be added to your servo assembly via:servo generate --defaults webhooks.Example Webhook RequestsTODO: Insert headers and request body for a couple of events</code></pre><h2>Installation</h2><p>servo-webhooks is distributed as an installable Python package via PyPi and can be added to a servo assembly via Poetry:</p><pre lang="console"><code>❯ poetry add servo-webhooksFor convenience, servo-webhooks is included in the default servox assemblyDocker images.UsageListing webhooks:servo webhooks listGetting event content type and payload schema:servo webhooks schema after:measureTriggering an ad-hoc webhook:servo webhooks trigger after:adjust ([NAME|URL])Implementing Webhook RespondersTODO: Content type, etc. headers. Include connector version, other event metadata. Schema versioning.Validating Webhook SignaturesAll webhook requests are sent with aX-Servo-Signatureheader. This value of this header is a hex
string representation of an HMAC SHA1 digest computed over the body of the request using the value of
thesecretkey from the webhook configuration. The signature can be easily verified to validate the
authenticity and integrity of the webhook payload. HMAC computation is supported on all major platforms
and in the standard library of most modern programming languages.An example of computing an HMAC SHA1 digest from a webhook request in Python looks like this:secret="super secret authentication code"expected_signature=request.headers["x-servo-signature"]body=request.read()signature=str(hmac.new(secret.encode(),body,hashlib.sha1).hexdigest())assertsignature==expected_signatureCancelling an Event via a WebhookLet's say that you want to implement a webhook that implements authorization of adjustments based on criteria
such as a schedule that only permits them during midnight and 3am. To implement this, the webhook responder will
return a 200 (OK) status code and a response body modeling aservo.errors.CancelEventErrorobject. Theservo-webhooksconnector will deserialize theCancelEventErrorrepresentation and raise aCancelEventErrorexception within the
assembly, cancelling the event. To indicate that your response body is a representation of aCancelEventErrorerror,
set theContent-Typeheader toapplication/vnd.opsani.servo.errors.CancelEventError+jsonand return a JSON object
that includes areasonproperty describing why the event was cancelled:
TODO: What's the best status code/response for cancellation?
Return a 200 (OK) response withContent-Typeof :> POST http://webhooks.example.com/servo-webhooks
> Content-Type: application/vnd.opsani.servo.events.Event+json # TODO: Not the right content type
> {
>  ...
> }

< 200 (OK)
< Content-Type: application/vnd.opsani.servo.errors.CancelEventError+json
< {
<  "reason": "Unable to authorize adjustment: Adjustments are only permitted between midnight and 3am."
< }Configuring Backoff Retries & TimeoutsTODO: Disabling backoff to avoid blocking on a before handler.Technical DetailsWebhook requests are managed non-persistently in memory. Requests are made via an asynchronoushttpxclient built on top ofasyncio. Support for webhook request body JSON Schema is provided via the
deep integration ofPydanticin servox. Backoff and retry supported is provided via thebackofflibrary.TestingAutomated tests are implemented viaPytest:pytest test_servo_webhooks.pyLicenseservo-webhooks is distributed under the terms of the Apache 2.0 Open Source license.A copy of the license is provided in theLICENSEfile at the root of the repository.