Repository: svix/svix-webhooks (language: rust)

Issue:
Java SDK | Getting "eu" authentication failed even though we are passing US API token.
## Bug Report
Java SDK | Getting "eu" authentication failed even though we are passing US API token.

### Version
0.69.0

### Description

We are using US environment for sending webhooks. While creating an instance of Svix we are passing **US** environment token still we are getting **_Invalid token for region "eu". (authentication_failed)_** message while performing any operations.

![image](https://user-images.githubusercontent.com/82813409/201018134-4b9bc23f-ca0d-431a-8c1c-5538d94fc7e3.png)

I debugged this code and came to know that there is a problem at 34th line , it is overriding the url at 34 which was set from 27th line.
So may be by fixing this , It might get resolved.

### Way around to resolve this 

Use below snippet to get Svix instance.

```
                SvixOptions svixOptions = new SvixOptions();
		svixOptions.setServerUrl("https://api.us.svix.com");
		Svix svix = new Svix(this.svixKey, svixOptions);
```
There is an ERROR in the redis migration - clean that up



Discussion / hints:

Fix patch:
diff --git a/.version b/.version
index acdcb8362..f91d144a8 100644
--- a/.version
+++ b/.version
@@ -1,1 +1,1 @@
-0.69.0
\ No newline at end of file
+0.71.0
\ No newline at end of file
diff --git a/ChangeLog.md b/ChangeLog.md
index dd74dd2f4..436efd39d 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -3,6 +3,16 @@
 ## Next
 * 
 
+## Version 0.71.0
+* Server: Add metadata to application CRUD
+* Server: Update Rust, sea_orm, and other deps
+* Libs: Update OpenAPI spec
+
+## Version 0.70.0
+* Server: create subcommand for wiping an organization's data
+* Server: internal code improvements
+* Libs/Java: fix issue with automatic region detection not working
+
 ## Version 0.69.0
 * Server: ensure QueueTasks are deleted after acknowledgement
 * Server: better handling of failures in streaming responses
diff --git a/csharp/Svix/Svix.csproj b/csharp/Svix/Svix.csproj
index fea5fb54e..77f2f2bee 100644
--- a/csharp/Svix/Svix.csproj
+++ b/csharp/Svix/Svix.csproj
@@ -3,7 +3,7 @@
   <PropertyGroup>
     <TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
     <PackageId>Svix</PackageId>
-    <Version>0.69.0</Version>
+    <Version>0.71.0</Version>
     <Authors>Svix</Authors>
     <Company>Svix</Company>
     <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
diff --git a/go/internal/openapi/api_authentication.go b/go/internal/openapi/api_authentication.go
index 52973e992..3251d4616 100644
--- a/go/internal/openapi/api_authentication.go
+++ b/go/internal/openapi/api_authentication.go
@@ -27,6 +27,184 @@ var (
 // AuthenticationApiService AuthenticationApi service
 type AuthenticationApiService service
 
+type ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest struct {
+	ctx _context.Context
+	ApiService *AuthenticationApiService
+	appId string
+	applicationTokenExpireIn *ApplicationTokenExpireIn
+	idempotencyKey *string
+}
+
+func (r ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest) ApplicationTokenExpireIn(applicationTokenExpireIn ApplicationTokenExpireIn) ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest {
+	r.applicationTokenExpireIn = &applicationTokenExpireIn
+	return r
+}
+func (r ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest) IdempotencyKey(idempotencyKey string) ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest {
+	r.idempotencyKey = &idempotencyKey
+	return r
+}
+
+func (r ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest) Execute() (*_nethttp.Response, error) {
+	return r.ApiService.ExpireAllApiV1AuthAppAppIdExpireAllPostExecute(r)
+}
+
+/*
+ * ExpireAllApiV1AuthAppAppIdExpireAllPost Expire All
+ * Expire all of the tokens associated with a specific Application
+ * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+ * @param appId
+ * @return ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest
+ */
+func (a *AuthenticationApiService) ExpireAllApiV1AuthAppAppIdExpireAllPost(ctx _context.Context, appId string) ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest {
+	return ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest{
+		ApiService: a,
+		ctx: ctx,
+		appId: appId,
+	}
+}
+
+/*
+ * Execute executes the request
+ */
+func (a *AuthenticationApiService) ExpireAllApiV1AuthAppAppIdExpireAllPostExecute(r ApiExpireAllApiV1AuthAppAppIdExpireAllPostRequest) (*_nethttp.Response, error) {
+	var (
+		localVarHTTPMethod   = _nethttp.MethodPost
+		localVarPostBody     interface{}
+		localVarFormFileName string
+		localVarFileName     string
+		localVarFileBytes    []byte
+	)
+
+	localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "AuthenticationApiService.ExpireAllApiV1AuthAppAppIdExpireAllPost")
+	if err != nil {
+		return nil, GenericOpenAPIError{error: err.Error()}
+	}
+
+	localVarPath := localBasePath + "/api/v1/auth/app/{app_id}/expire-all/"
+	localVarPath = strings.Replace(localVarPath, "{"+"app_id"+"}", _neturl.PathEscape(parameterToString(r.appId, "")), -1)
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := _neturl.Values{}
+	localVarFormParams := _neturl.Values{}
+	if strlen(r.appId) < 1 {
+		return nil, reportError("appId must have at least 1 elements")
+	}
+	if strlen(r.appId) > 256 {
+		return nil, reportError("appId must have less than 256 elements")
+	}
+	if r.applicationTokenExpireIn == nil {
+		return nil, reportError("applicationTokenExpireIn is required and must be specified")
+	}
+
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{"application/json"}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	if r.idempotencyKey != nil {
+		localVarHeaderParams["idempotency-key"] = parameterToString(*r.idempotencyKey, "")
+	}
+	// body params
+	localVarPostBody = r.applicationTokenExpireIn
+	req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(req)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarHTTPResponse, err
+	}
+
+	localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	localVarHTTPResponse.Body = _ioutil.NopCloser(bytes.NewBuffer(localVarBody))
+	if err != nil {
+		return localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 401 {
+			var v HttpErrorOut
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 403 {
+			var v HttpErrorOut
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 404 {
+			var v HttpErrorOut
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 409 {
+			var v HttpErrorOut
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 422 {
+			var v HTTPValidationError
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarHTTPResponse, newErr
+		}
+		if localVarHTTPResponse.StatusCode == 429 {
+			var v HttpErrorOut
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+		}
+		return localVarHTTPResponse, newErr
+	}
+
+	return localVarHTTPResponse, nil
+}
+
 type ApiGetDashboardAccessApiV1AuthDashboardAccessAppIdPostRequest struct {
 	ctx _context.Context
 	ApiService *AuthenticationApiService
diff --git a/go/internal/openapi/api_endpoint.go b/go/internal/openapi/api_endpoint.go
index bafbe8d7e..19594695b 100644
--- a/go/internal/openapi/api_endpoint.go
+++ b/go/internal/openapi/api_endpoint.go
@@ -2845,6 +2845,16 @@ func (a *EndpointApiService) SimulateEndpointTransformationApiV1AppAppIdEndpoint
 			body:  localVarBody,
 			error: localVarHTTPResponse.Status,
 		}
+		if localVarHTTPResponse.StatusCode == 400 {
+			var v HttpErrorOut
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+			return localVarReturnValue, localVarHTTPResponse, newErr
+		}
 		if localVarHTTPResponse.StatusCode == 401 {
 			var v HttpErrorOut
 			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
diff --git a/go/internal/openapi/api_message.go b/go/internal/openapi/api_message.go
index 38adef8f0..271a5f83e 100644
--- a/go/internal/openapi/api_message.go
+++ b/go/internal/openapi/api_message.go
@@ -65,7 +65,7 @@ If a message with the same `eventId` already exists for any application in your
 The `eventType` indicates the type and schema of the event. All messages of a certain `eventType` are expected to have the same schema. Endpoints can choose to only listen to specific event types.
 Messages can also have `channels`, which similar to event types let endpoints filter by them. Unlike event types, messages can have multiple channels, and channels don't imply a specific message content or schema.
 
-The `payload' property is the webhook's body (the actual webhook message).
+The `payload` property is the webhook's body (the actual webhook message). Svix supports payload sizes of up to ~350kb, though it's generally a good idea to keep webhook payloads small, probably no larger than 40kb.
  * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
  * @param appId
  * @return ApiCreateMessageApiV1AppAppIdMsgPostRequest
diff --git a/go/internal/openapi/model_application_token_expire_in.go b/go/internal/openapi/model_application_token_expire_in.go
new file mode 100644
index 000000000..8decbb917
--- /dev/null
+++ b/go/internal/openapi/model_application_token_expire_in.go
@@ -0,0 +1,126 @@
+/*
+ * Svix API
+ *
+ * Welcome to the Svix API documentation!  Useful links: [Homepage](https://www.svix.com) | [Support email](mailto:support+docs@svix.com) | [Blog](https://www.svix.com/blog/) | [Slack Community](https://www.svix.com/slack/)  # Introduction  This is the reference documentation and schemas for the [Svix webhook service](https://www.svix.com) API. For tutorials and other documentation please refer to [the documentation](https://docs.svix.com).  ## Main concepts  In Svix you have four important entities you will be interacting with:  - `messages`: these are the webhooks being sent. They can have contents and a few other properties. - `application`: this is where `messages` are sent to. Usually you want to create one application for each user on your platform. - `endpoint`: endpoints are the URLs messages will be sent to. Each application can have multiple `endpoints` and each message sent to that application will be sent to all of them (unless they are not subscribed to the sent event type). - `event-type`: event types are identifiers denoting the type of the message being sent. Event types are primarily used to decide which events are sent to which endpoint.   ## Authentication  Get your authentication token (`AUTH_TOKEN`) from the [Svix dashboard](https://dashboard.svix.com) and use it as part of the `Authorization` header as such: `Authorization: Bearer ${AUTH_TOKEN}`.  <SecurityDefinitions />   ## Code samples  The code samples assume you already have the respective libraries installed and you know how to use them. For the latest information on how to do that, please refer to [the documentation](https://docs.svix.com/).   ## Idempotency  Svix supports [idempotency](https://en.wikipedia.org/wiki/Idempotence) for safely retrying requests without accidentally performing the same operation twice. This is useful when an API call is disrupted in transit and you do not receive a response.  To perform an idempotent request, pass the idempotency key in the `Idempotency-Key` header to the request. The idempotency key should be a unique value generated by the client. You can create the key in however way you like, though we suggest using UUID v4, or any other string with enough entropy to avoid collisions.  Svix's idempotency works by saving the resulting status code and body of the first request made for any given idempotency key for any successful request. Subsequent requests with the same key return the same result.  Please note that idempotency is only supported for `POST` requests.   ## Cross-Origin Resource Sharing  This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/). And that allows cross-domain communication from the browser. All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site. 
+ *
+ * API version: 1.4
+ */
+
+// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
+
+package openapi
+
+import (
+	"encoding/json"
+)
+
+// ApplicationTokenExpireIn struct for ApplicationTokenExpireIn
+type ApplicationTokenExpireIn struct {
+	// How many seconds until the old key is expired.
+	Expiry NullableInt32 `json:"expiry,omitempty"`
+}
+
+// NewApplicationTokenExpireIn instantiates a new ApplicationTokenExpireIn object
+// This constructor will assign default values to properties that have it defined,
+// and makes sure properties required by API are set, but the set of arguments
+// will change when the set of required properties is changed
+func NewApplicationTokenExpireIn() *ApplicationTokenExpireIn {
+	this := ApplicationTokenExpireIn{}
+	return &this
+}
+
+// NewApplicationTokenExpireInWithDefaults instantiates a new ApplicationTokenExpireIn object
+// This constructor will only assign default values to properties that have it defined,
+// but it doesn't guarantee that properties required by API are set
+func NewApplicationTokenExpireInWithDefaults() *ApplicationTokenExpireIn {
+	this := ApplicationTokenExpireIn{}
+	return &this
+}
+
+// GetExpiry returns the Expiry field value if set, zero value otherwise (both if not set or set to explicit null).
+func (o *ApplicationTokenExpireIn) GetExpiry() int32 {
+	if o == nil || o.Expiry.Get() == nil {
+		var ret int32
+		return ret
+	}
+	return *o.Expiry.Get()
+}
+
+// GetExpiryOk returns a tuple with the Expiry field value if set, nil otherwise
+// and a boolean to check if the value has been set.
+// NOTE: If the value is an explicit nil, `nil, true` will be returned
+func (o *ApplicationTokenExpireIn) GetExpiryOk() (*int32, bool) {
+	if o == nil  {
+		return nil, false
+	}
+	return o.Expiry.Get(), o.Expiry.IsSet()
+}
+
+// HasExpiry returns a boolean if a field has been set.
+func (o *ApplicationTokenExpireIn) HasExpiry() bool {
+	if o != nil && o.Expiry.IsSet() {
+		return true
+	}
+
+	return false
+}
+
+// SetExpiry gets a reference to the given NullableInt32 and assigns it to the Expiry field.
+func (o *ApplicationTokenExpireIn) SetExpiry(v int32) {
+	o.Expiry.Set(&v)
+}
+// SetExpiryNil sets the value for Expiry to be an explicit nil
+func (o *ApplicationTokenExpireIn) SetExpiryNil() {
+	o.Expiry.Set(nil)
+}
+
+// UnsetExpiry ensures that no value is present for Expiry, not even an explicit nil
+func (o *ApplicationTokenExpireIn) UnsetExpiry() {
+	o.Expiry.Unset()
+}
+
+func (o ApplicationTokenExpireIn) MarshalJSON() ([]byte, error) {
+	toSerialize := map[string]interface{}{}
+	if o.Expiry.IsSet() {
+		toSerialize["expiry"] = o.Expiry.Get()
+	}
+	return json.Marshal(toSerialize)
+}
+
+type NullableApplicationTokenExpireIn struct {
+	value *ApplicationTokenExpireIn
+	isSet bool
+}
+
+func (v NullableApplicationTokenExpireIn) Get() *ApplicationTokenExpireIn {
+	return v.value
+}
+
+func (v *NullableApplicationTokenExpireIn) Set(val *ApplicationTokenExpireIn) {
+	v.value = val
+	v.isSet = true
+}
+
+func (v NullableApplicationTokenExpireIn) IsSet() bool {
+	return v.isSet
+}
+
+func (v *NullableApplicationTokenExpireIn) Unset() {
+	v.value = nil
+	v.isSet = false
+}
+
+func NewNullableApplicationTokenExpireIn(val *ApplicationTokenExpireIn) *NullableApplicationTokenExpireIn {
+	return &NullableApplicationTokenExpireIn{value: val, isSet: true}
+}
+
+func (v NullableApplicationTokenExpireIn) MarshalJSON() ([]byte, error) {
+	return json.Marshal(v.value)
+}
+
+func (v *NullableApplicationTokenExpireIn) UnmarshalJSON(src []byte) error {
+	v.isSet = true
+	return json.Unmarshal(src, &v.value)
+}
+
+
diff --git a/go/internal/openapi/model_settings_in.go b/go/internal/openapi/model_settings_in.go
index b323a47e2..1091f098f 100644
--- a/go/internal/openapi/model_settings_in.go
+++ b/go/internal/openapi/model_settings_in.go
@@ -29,7 +29,7 @@ type SettingsIn struct {
 	EnableIntegrationManagement *bool `json:"enableIntegrationManagement,omitempty"`
 	EnableTransformations *bool `json:"enableTransformations,omitempty"`
 	EnforceHttps *bool `json:"enforceHttps,omitempty"`
-	EventCatalogPublished NullableBool `json:"eventCatalogPublished,omitempty"`
+	EventCatalogPublished *bool `json:"eventCatalogPublished,omitempty"`
 	ReadOnly *bool `json:"readOnly,omitempty"`
 }
 
@@ -50,7 +50,7 @@ func NewSettingsIn() *SettingsIn {
 	var enforceHttps bool = true
 	this.EnforceHttps = &enforceHttps
 	var eventCatalogPublished bool = false
-	this.EventCatalogPublished = *NewNullableBool(&eventCatalogPublished)
+	this.EventCatalogPublished = &eventCatalogPublished
 	var readOnly bool = false
 	this.ReadOnly = &readOnly
 	return &this
@@ -72,7 +72,7 @@ func NewSettingsInWithDefaults() *SettingsIn {
 	var enforceHttps bool = true
 	this.EnforceHttps = &enforceHttps
 	var eventCatalogPublished bool = false
-	this.EventCatalogPublished = *NewNullableBool(&eventCatalogPublished)
+	this.EventCatalogPublished = &eventCatalogPublished
 	var readOnly bool = false
 	this.ReadOnly = &readOnly
 	return &this
@@ -544,46 +544,36 @@ func (o *SettingsIn) SetEnforceHttps(v bool) {
 	o.EnforceHttps = &v
 }
 
-// GetEventCatalogPublished returns the EventCatalogPublished field value if set, zero value otherwise (both if not set or set to explicit null).
+// GetEventCatalogPublished returns the EventCatalogPublished field value if set, zero value otherwise.
 func (o *SettingsIn) GetEventCatalogPublished() bool {
-	if o == nil || o.EventCatalogPublished.Get() == nil {
+	if o == nil || o.EventCatalogPublished == nil {
 		var ret bool
 		return ret
 	}
-	return *o.EventCatalogPublished.Get()
+	return *o.EventCatalogPublished
 }
 
 // GetEventCatalogPublishedOk returns a tuple with the EventCatalogPublished field value if set, nil otherwise
 // and a boolean to check if the value has been set.
-// NOTE: If the value is an explicit nil, `nil, true` will be returned
 func (o *SettingsIn) GetEventCatalogPublishedOk() (*bool, bool) {
-	if o == nil  {
+	if o == nil || o.EventCatalogPublished == nil {
 		return nil, false
 	}
-	return o.EventCatalogPublished.Get(), o.EventCatalogPublished.IsSet()
+	return o.EventCatalogPublished, true
 }
 
 // HasEventCatalogPublished returns a boolean if a field has been set.
 func (o *SettingsIn) HasEventCatalogPublished() bool {
-	if o != nil && o.EventCatalogPublished.IsSet() {
+	if o != nil && o.EventCatalogPublished != nil {
 		return true
 	}
 
 	return false
 }
 
-// SetEventCatalogPublished gets a reference to the given NullableBool and assigns it to the EventCatalogPublished field.
+// SetEventCatalogPublished gets a reference to the given bool and assigns it to the EventCatalogPublished field.
 func (o *SettingsIn) SetEventCatalogPublished(v bool) {
-	o.EventCatalogPublished.Set(&v)
-}
-// SetEventCatalogPublishedNil sets the value for EventCatalogPublished to be an explicit nil
-func (o *SettingsIn) SetEventCatalogPublishedNil() {
-	o.EventCatalogPublished.Set(nil)
-}
-
-// UnsetEventCatalogPublished ensures that no value is present for EventCatalogPublished, not even an explicit nil
-func (o *SettingsIn) UnsetEventCatalogPublished() {
-	o.EventCatalogPublished.Unset()
+	o.EventCatalogPublished = &v
 }
 
 // GetReadOnly returns the ReadOnly field value if set, zero value otherwise.
@@ -659,8 +649,8 @@ func (o SettingsIn) MarshalJSON() ([]byte, error) {
 	if o.EnforceHttps != nil {
 		toSerialize["enforceHttps"] = o.EnforceHttps
 	}
-	if o.EventCatalogPublished.IsSet() {
-		toSerialize["eventCatalogPublished"] = o.EventCatalogPublished.Get()
+	if o.EventCatalogPublished != nil {
+		toSerialize["eventCatalogPublished"] = o.EventCatalogPublished
 	}
 	if o.ReadOnly != nil {
 		toSerialize["readOnly"] = o.ReadOnly
diff --git a/go/internal/openapi/model_settings_out.go b/go/internal/openapi/model_settings_out.go
index 3693572eb..f32b6d90e 100644
--- a/go/internal/openapi/model_settings_out.go
+++ b/go/internal/openapi/model_settings_out.go
@@ -29,7 +29,7 @@ type SettingsOut struct {
 	EnableIntegrationManagement *bool `json:"enableIntegrationManagement,omitempty"`
 	EnableTransformations *bool `json:"enableTransformations,omitempty"`
 	EnforceHttps *bool `json:"enforceHttps,omitempty"`
-	EventCatalogPublished NullableBool `json:"eventCatalogPublished,omitempty"`
+	EventCatalogPublished *bool `json:"eventCatalogPublished,omitempty"`
 	ReadOnly *bool `json:"readOnly,omitempty"`
 }
 
@@ -50,7 +50,7 @@ func NewSettingsOut() *SettingsOut {
 	var enforceHttps bool = true
 	this.EnforceHttps = &enforceHttps
 	var eventCatalogPublished bool = false
-	this.EventCatalogPublished = *NewNullableBool(&eventCatalogPublished)
+	this.EventCatalogPublished = &eventCatalogPublished
 	var readOnly bool = false
 	this.ReadOnly = &readOnly
 	return &this
@@ -72,7 +72,7 @@ func NewSettingsOutWithDefaults() *SettingsOut {
 	var enforceHttps bool = true
 	this.EnforceHttps = &enforceHttps
 	var eventCatalogPublished bool = false
-	this.EventCatalogPublished = *NewNullableBool(&eventCatalogPublished)
+	this.EventCatalogPublished = &eventCatalogPublished
 	var readOnly bool = false
 	this.ReadOnly = &readOnly
 	return &this
@@ -544,46 +544,36 @@ func (o *SettingsOut) SetEnforceHttps(v bool) {
 	o.EnforceHttps = &v
 }
 
-// GetEventCatalogPublished returns the EventCatalogPublished field value if set, zero value otherwise (both if not set or set to explicit null).
+// GetEventCatalogPublished returns the EventCatalogPublished field value if set, zero value otherwise.
 func (o *SettingsOut) GetEventCatalogPublished() bool {
-	if o == nil || o.EventCatalogPublished.Get() == nil {
+	if o == nil || o.EventCatalogPublished == nil {
 		var ret bool
 		return ret
 	}
-	return *o.EventCatalogPublished.Get()
+	return *o.EventCatalogPublished
 }
 
 // GetEventCatalogPublishedOk returns a tuple with the EventCatalogPublished field value if set, nil otherwise
 // and a boolean to check if the value has been set.
-// NOTE: If the value is an explicit nil, `nil, true` will be returned
 func (o *SettingsOut) GetEventCatalogPublishedOk() (*bool, bool) {
-	if o == nil  {
+	if o == nil || o.EventCatalogPublished == nil {
 		return nil, false
 	}
-	return o.EventCatalogPublished.Get(), o.EventCatalogPublished.IsSet()
+	return o.EventCatalogPublished, true
 }
 
 // HasEventCatalogPublished returns a boolean if a field has been set.
 func (o *SettingsOut) HasEventCatalogPublished() bool {
-	if o != nil && o.EventCatalogPublished.IsSet() {
+	if o != nil && o.EventCatalogPublished != nil {
 		return true
 	}
 
 	return false
 }
 
-// SetEventCatalogPublished gets a reference to the given NullableBool and assigns it to the EventCatalogPublished field.
+// SetEventCatalogPublished gets a reference to the given bool and assigns it to the EventCatalogPublished field.
 func (o *SettingsOut) SetEventCatalogPublished(v bool) {
-	o.EventCatalogPublished.Set(&v)
-}
-// SetEventCatalogPublishedNil sets the value for EventCatalogPublished to be an explicit nil
-func (o *SettingsOut) SetEventCatalogPublishedNil() {
-	o.EventCatalogPublished.Set(nil)
-}
-
-// UnsetEventCatalogPublished ensures that no value is present for EventCatalogPublished, not even an explicit nil
-func (o *SettingsOut) UnsetEventCatalogPublished() {
-	o.EventCatalogPublished.Unset()
+	o.EventCatalogPublished = &v
 }
 
 // GetReadOnly returns the ReadOnly field value if set, zero value otherwise.
@@ -659,8 +649,8 @@ func (o SettingsOut) MarshalJSON() ([]byte, error) {
 	if o.EnforceHttps != nil {
 		toSerialize["enforceHttps"] = o.EnforceHttps
 	}
-	if o.EventCatalogPublished.IsSet() {
-		toSerialize["eventCatalogPublished"] = o.EventCatalogPublished.Get()
+	if o.EventCatalogPublished != nil {
+		toSerialize["eventCatalogPublished"] = o.EventCatalogPublished
 	}
 	if o.ReadOnly != nil {
 		toSerialize["readOnly"] = o.ReadOnly
diff --git a/go/internal/version/version.go b/go/internal/version/version.go
index bf2de2b51..50b0332d6 100644
--- a/go/internal/version/version.go
+++ b/go/internal/version/version.go
@@ -1,3 +1,3 @@
 package version
 
-const Version = "0.69.0"
+const Version = "0.71.0"
diff --git a/java/README.md b/java/README.md
index a307b2998..67e968ace 100644
--- a/java/README.md
+++ b/java/README.md
@@ -51,7 +51,7 @@ Add this dependency to your project's POM:
 <dependency>
   <groupId>com.svix</groupId>
   <artifactId>svix</artifactId>
-  <version>0.69.0</version>
+  <version>0.71.0</version>
   <scope>compile</scope>
 </dependency>
 ```
@@ -61,7 +61,7 @@ Add this dependency to your project's POM:
 Add this dependency to your project's build file:
 
 ```groovy
-implementation "com.svix:svix:0.69.0"
+implementation "com.svix:svix:0.71.0"
 ```
 
 # Development
diff --git a/java/gradle.properties b/java/gradle.properties
index 1ff5e4d70..e70daf3b3 100644
--- a/java/gradle.properties
+++ b/java/gradle.properties
@@ -1,5 +1,5 @@
 GROUP=com.svix
-VERSION_NAME=0.69.0
+VERSION_NAME=0.71.0
 
 POM_URL=https://github.com/svix/svix-webhooks
 POM_SCM_URL=git@github.com:svix/svix-webhooks.git
diff --git a/java/lib/src/main/java/com/svix/Svix.java b/java/lib/src/main/java/com/svix/Svix.java
index 4247ca257..7f3a7f102 100644
--- a/java/lib/src/main/java/com/svix/Svix.java
+++ b/java/lib/src/main/java/com/svix/Svix.java
@@ -5,7 +5,7 @@
 import com.svix.internal.auth.HttpBearerAuth;
 
 public final class Svix {
-	public static final String VERSION = "0.69.0";
+	public static final String VERSION = "0.71.0";
 	private final Application application;
 	private final Authentication authentication;
 	private final Endpoint endpoint;
@@ -29,9 +29,10 @@ public Svix(final String token, final SvixOptions options) {
 			apiClient.setBasePath("https://api.eu.svix.com");
 		} else if (region.equals("in")) {
 			apiClient.setBasePath("https://api.in.svix.com");
+		} else {
+			apiClient.setBasePath(options.getServerUrl());
 		}
 
-		apiClient.setBasePath(options.getServerUrl());
 		apiClient.setUserAgent(String.format("svix-libs/%s/java", Svix.VERSION));
 
 		HttpBearerAuth httpBearer = (HttpBearerAuth) apiClient.getAuthentication("HTTPBearer");
diff --git a/javascript/package.json b/javascript/package.json
index 972cdd06a..652563b6b 100644
--- a/javascript/package.json
+++ b/javascript/package.json
@@ -1,6 +1,6 @@
 {
   "name": "svix",
-  "version": "0.69.0",
+  "version": "0.71.0",
   "description": "Svix API client",
   "author": "svix",
   "repository": "https://github.com/svix/svix-libs",
diff --git a/javascript/src/index.ts b/javascript/src/index.ts
index 993db1fd9..d643c56fb 100644
--- a/javascript/src/index.ts
+++ b/javascript/src/index.ts
@@ -55,7 +55,7 @@ import * as base64 from "@stablelib/base64";
 import * as sha256 from "fast-sha256";
 
 const WEBHOOK_TOLERANCE_IN_SECONDS = 5 * 60; // 5 minutes
-const VERSION = "0.69.0";
+const VERSION = "0.71.0";
 
 class UserAgentMiddleware implements Middleware {
   public pre(context: RequestContext): Promise<RequestContext> {
diff --git a/javascript/yarn.lock b/javascript/yarn.lock
index 30193f2c2..54b0ae3fa 100644
--- a/javascript/yarn.lock
+++ b/javascript/yarn.lock
@@ -944,9 +944,9 @@ babel-preset-jest@^27.0.1:
     babel-preset-current-node-syntax "^1.0.0"
 
 balanced-match@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
-  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
 brace-expansion@^1.1.7:
   version "1.1.11"
@@ -1108,7 +1108,7 @@ combined-stream@^1.0.8:
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+  integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
 convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
   version "1.7.0"
@@ -2416,9 +2416,9 @@ mimic-fn@^2.1.0:
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
 minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
   dependencies:
     brace-expansion "^1.1.7"
 
diff --git a/kotlin/README.md b/kotlin/README.md
index 22c1ae3af..9c66dc26b 100644
--- a/kotlin/README.md
+++ b/kotlin/README.md
@@ -51,7 +51,7 @@ Add this dependency to your project's POM:
 <dependency>
   <groupId>com.svix.kotlin</groupId>
   <artifactId>svix-kotlin</artifactId>
-  <version>0.69.0</version>
+  <version>0.71.0</version>
   <scope>compile</scope>
 </dependency>
 ```
@@ -61,7 +61,7 @@ Add this dependency to your project's POM:
 Add this dependency to your project's build file:
 
 ```groovy
-implementation "com.svix.kotlin:svix-kotlin:0.69.0"
+implementation "com.svix.kotlin:svix-kotlin:0.71.0"
 ```
 
 # Development
diff --git a/kotlin/gradle.properties b/kotlin/gradle.properties
index effa2f02c..3c8944f6f 100644
--- a/kotlin/gradle.properties
+++ b/kotlin/gradle.properties
@@ -1,5 +1,5 @@
 GROUP=com.svix.kotlin
-VERSION_NAME=0.69.0
+VERSION_NAME=0.71.0
 
 POM_URL=https://github.com/svix/svix-webhooks
 POM_SCM_URL=git@github.com:svix/svix-webhooks.git
diff --git a/kotlin/lib/src/main/kotlin/SvixOptions.kt b/kotlin/lib/src/main/kotlin/SvixOptions.kt
index 9fbbee3b5..006d03c60 100644
--- a/kotlin/lib/src/main/kotlin/SvixOptions.kt
+++ b/kotlin/lib/src/main/kotlin/SvixOptions.kt
@@ -6,7 +6,7 @@ data class SvixOptions(
     val numRetries: Int? = null
 ) {
 
-    private val version = "0.69.0"
+    private val version = "0.71.0"
 
     var serverUrl: String
         get() = this.wantedServerUrl ?: DEFAULT_URL
diff --git a/openapi.json b/openapi.json
index d884af683..d17aa9013 100644
--- a/openapi.json
+++ b/openapi.json
@@ -126,6 +126,21 @@
                 "title": "ApplicationStats",
                 "type": "object"
             },
+            "ApplicationTokenExpireIn": {
+                "properties": {
+                    "expiry": {
+                        "description": "How many seconds until the old key is expired.",
+                        "example": 60,
+                        "maximum": 259200.0,
+                        "minimum": 0.0,
+                        "nullable": true,
+                        "title": "Expiry",
+                        "type": "integer"
+                    }
+                },
+                "title": "ApplicationTokenExpireIn",
+                "type": "object"
+            },
             "AttemptStatisticsData": {
                 "properties": {
                     "failureCount": {
@@ -2742,7 +2757,6 @@
                     },
                     "eventCatalogPublished": {
                         "default": false,
-                        "nullable": true,
                         "title": "Eventcatalogpublished",
                         "type": "boolean"
                     },
@@ -2827,7 +2841,6 @@
                     },
                     "eventCatalogPublished": {
                         "default": false,
-                        "nullable": true,
                         "title": "Eventcatalogpublished",
                         "type": "boolean"
                     },
@@ -7941,6 +7954,16 @@
                         },
                         "description": "Successful Response"
                     },
+                    "400": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HttpErrorOut"
+                                }
+                            }
+                        },
+                        "description": "Bad Request"
+                    },
                     "401": {
                         "content": {
                             "application/json": {
@@ -9642,7 +9665,7 @@
                 ]
             },
             "post": {
-                "description": "Creates a new message and dispatches it to all of the application's endpoints.\n\nThe `eventId` is an optional custom unique ID. It's verified to be unique only up to a day, after that no verification will be made.\nIf a message with the same `eventId` already exists for any application in your environment, a 409 conflict error will be returned.\n\nThe `eventType` indicates the type and schema of the event. All messages of a certain `eventType` are expected to have the same schema. Endpoints can choose to only listen to specific event types.\nMessages can also have `channels`, which similar to event types let endpoints filter by them. Unlike event types, messages can have multiple channels, and channels don't imply a specific message content or schema.\n\nThe `payload' property is the webhook's body (the actual webhook message).",
+                "description": "Creates a new message and dispatches it to all of the application's endpoints.\n\nThe `eventId` is an optional custom unique ID. It's verified to be unique only up to a day, after that no verification will be made.\nIf a message with the same `eventId` already exists for any application in your environment, a 409 conflict error will be returned.\n\nThe `eventType` indicates the type and schema of the event. All messages of a certain `eventType` are expected to have the same schema. Endpoints can choose to only listen to specific event types.\nMessages can also have `channels`, which similar to event types let endpoints filter by them. Unlike event types, messages can have multiple channels, and channels don't imply a specific message content or schema.\n\nThe `payload` property is the webhook's body (the actual webhook message). Svix supports payload sizes of up to ~350kb, though it's generally a good idea to keep webhook payloads small, probably no larger than 40kb.",
                 "operationId": "create_message_api_v1_app__app_id__msg__post",
                 "parameters": [
                     {
@@ -11470,6 +11493,186 @@
                 ]
             }
         },
+        "/api/v1/auth/app/{app_id}/expire-all/": {
+            "post": {
+                "description": "Expire all of the tokens associated with a specific Application",
+                "operationId": "expire_all_api_v1_auth_app__app_id__expire_all__post",
+                "parameters": [
+                    {
+                        "in": "path",
+                        "name": "app_id",
+                        "required": true,
+                        "schema": {
+                            "description": "The application's ID or UID",
+                            "example": "app_1srOrx2ZWZBpBUvZwXKQmoEYga2",
+                            "maxLength": 256,
+                            "minLength": 1,
+                            "pattern": "^[a-zA-Z0-9\\-_.]+$",
+                            "title": "App Id",
+                            "type": "string"
+                        }
+                    },
+                    {
+                        "description": "The request's idempotency key",
+                        "in": "header",
+                        "name": "idempotency-key",
+                        "required": false,
+                        "schema": {
+                            "description": "The request's idempotency key",
+                            "nullable": true,
+                            "title": "Idempotency-Key",
+                            "type": "string"
+                        }
+                    }
+                ],
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "$ref": "#/components/schemas/ApplicationTokenExpireIn"
+                            }
+                        }
+                    },
+                    "required": true
+                },
+                "responses": {
+                    "204": {
+                        "description": "Successful Response"
+                    },
+                    "401": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HttpErrorOut"
+                                }
+                            }
+                        },
+                        "description": "Unauthorized"
+                    },
+                    "403": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HttpErrorOut"
+                                }
+                            }
+                        },
+                        "description": "Forbidden"
+                    },
+                    "404": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HttpErrorOut"
+                                }
+                            }
+                        },
+                        "description": "Not Found"
+                    },
+                    "409": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HttpErrorOut"
+                                }
+                            }
+                        },
+                        "description": "Conflict"
+                    },
+                    "422": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                        "description": "Validation Error"
+                    },
+                    "429": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HttpErrorOut"
+                                }
+                            }
+                        },
+                        "description": "Too Many Requests"
+                    }
+                },
+                "security": [
+                    {
+                        "HTTPBearer": []
+                    }
+                ],
+                "summary": "Expire All",
+                "tags": [
+                    "Authentication"
+                ],
+                "x-codeSamples": [
+                    {
+                        "label": "JavaScript",
+                        "lang": "JavaScript",
+                        "source": "await svix.authentication.expireAll('app_id', {\n    expiry: 60\n});"
+                    },
+                    {
+                        "label": "TypeScript",
+                        "lang": "JavaScript",
+                        "source": "await svix.authentication.expireAll('app_id', {\n    expiry: 60\n});"
+                    },
+                    {
+                        "label": "Python",
+                        "lang": "Python",
+                        "source": "svix.authentication.expire_all('app_id', ApplicationTokenExpireIn(\n    expiry=60\n))"
+                    },
+                    {
+                        "label": "Python (Async)",
+                        "lang": "Python",
+                        "source": "await svix.authentication.expire_all('app_id', ApplicationTokenExpireIn(\n    expiry=60\n))"
+                    },
+                    {
+                        "label": "Go",
+                        "lang": "Go",
+                        "source": "err := svixClient.Authentication.ExpireAll(\"app_id\", &svix.ApplicationTokenExpireIn{\n    Expiry: 60\n})"
+                    },
+                    {
+                        "label": "Kotlin",
+                        "lang": "Kotlin",
+                        "source": "svix.authentication.expireAll('app_id', ApplicationTokenExpireIn()\n    .expiry(60)\n)"
+                    },
+                    {
+                        "label": "Java",
+                        "lang": "Java",
+                        "source": "svix.getAuthentication().expireAll('app_id', new ApplicationTokenExpireIn()\n    .expiry(60)\n)"
+                    },
+                    {
+                        "label": "Ruby",
+                        "lang": "Ruby",
+                        "source": "svix.authentication.expire_all('app_id', Svix::ApplicationTokenExpireIn.new({\n    \"expiry\": 60\n}))"
+                    },
+                    {
+                        "label": "Rust",
+                        "lang": "Rust",
+                        "source": "svix.authentication().expire_all(\"app_id\", ApplicationTokenExpireIn {\n    expiry: 60\n}).await?;"
+                    },
+                    {
+                        "label": "C#",
+                        "lang": "C#",
+                        "source": "await svix.Authentication.ExpireAllAsync(\"app_id\", new ApplicationTokenExpireIn{\n    expiry: 60\n})"
+                    },
+                    {
+                        "label": "CLI",
+                        "lang": "Shell",
+                        "source": "svix authentication expire-all 'app_id' '{\n    \"expiry\": 60\n}'"
+                    },
+                    {
+                        "label": "cURL",
+                        "lang": "Shell",
+                        "source": "curl -X 'POST' \\\n  'https://api.svix.com/api/v1/auth/app/{app_id}/expire-all/' \\\n  -H 'Authorization: Bearer AUTH_TOKEN' \\\n  -H 'accept: application/json' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"expiry\": 60\n}'"
+                    }
+                ]
+            }
+        },
         "/api/v1/auth/dashboard-access/{app_id}/": {
             "post": {
                 "description": "Use this function to get magic links (and authentication codes) for connecting your users to the Consumer Application Portal.",
diff --git a/python/svix/__init__.py b/python/svix/__init__.py
index e34aa7e0b..b7fa28b05 100644
--- a/python/svix/__init__.py
+++ b/python/svix/__init__.py
@@ -37,4 +37,4 @@
     "WebhookVerificationError",
 ]
 
-__version__ = "0.69.0"
+__version__ = "0.71.0"
diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock
index 0dafde31b..de21e4cb2 100644
--- a/ruby/Gemfile.lock
+++ b/ruby/Gemfile.lock
@@ -1,7 +1,7 @@
 PATH
   remote: .
   specs:
-    svix (0.69.0)
+    svix (0.71.0)
       typhoeus (~> 1.0, >= 1.0.1)
 
 GEM
diff --git a/ruby/lib/svix/version.rb b/ruby/lib/svix/version.rb
index 988db57cc..1b97965b6 100644
--- a/ruby/lib/svix/version.rb
+++ b/ruby/lib/svix/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Svix
-  VERSION = "0.69.0"
+  VERSION = "0.71.0"
 end
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 0e4d754fa..850a3e23b 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "svix"
-version = "0.69.0"
+version = "0.71.0"
 authors = ["Svix Inc. <oss@svix.com>"]
 edition = "2021"
 description="Rust library for interacting with the Svix API and verifying webhook signatures."
diff --git a/rust/src/api.rs b/rust/src/api.rs
index d1367b2cf..afe265cb4 100644
--- a/rust/src/api.rs
+++ b/rust/src/api.rs
@@ -42,7 +42,7 @@ impl Svix {
         });
         let cfg = Configuration {
             base_path,
-            user_agent: Some(format!("svix-libs/{}/rust", CRATE_VERSION)),
+            user_agent: Some(format!("svix-libs/{CRATE_VERSION}/rust")),
             bearer_access_token: Some(token),
             ..Configuration::default()
         };
diff --git a/rust/src/webhooks.rs b/rust/src/webhooks.rs
index 13e55642e..b9a114c02 100644
--- a/rust/src/webhooks.rs
+++ b/rust/src/webhooks.rs
@@ -102,11 +102,11 @@ impl Webhook {
         payload: &[u8],
     ) -> Result<String, WebhookError> {
         let payload = std::str::from_utf8(payload).map_err(|_| WebhookError::InvalidPayload)?;
-        let to_sign = format!("{}.{}.{}", msg_id, timestamp, payload,);
+        let to_sign = format!("{msg_id}.{timestamp}.{payload}",);
         let signed = hmac_sha256::HMAC::mac(to_sign.as_bytes(), &self.key);
         let encoded = base64::encode(signed);
 
-        Ok(format!("{},{}", SIGNATURE_VERSION, encoded))
+        Ok(format!("{SIGNATURE_VERSION},{encoded}"))
     }
 
     fn get_header<'a>(
diff --git a/server/Cargo.lock b/server/Cargo.lock
index fa515ef9a..f7e5e57b2 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -30,9 +30,9 @@ dependencies = [
 
 [[package]]
 name = "ahash"
-version = "0.8.0"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72"
+checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
 dependencies = [
  "cfg-if",
  "getrandom",
@@ -43,9 +43,9 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.19"
+version = "0.7.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
 dependencies = [
  "memchr",
 ]
@@ -65,26 +65,11 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "ansi_term"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
-dependencies = [
- "winapi",
-]
-
 [[package]]
 name = "anyhow"
-version = "1.0.64"
+version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7"
-
-[[package]]
-name = "arrayvec"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
 
 [[package]]
 name = "async-stream"
@@ -109,9 +94,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.57"
+version = "0.1.59"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f"
+checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -120,9 +105,9 @@ dependencies = [
 
 [[package]]
 name = "atoi"
-version = "0.4.0"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5"
+checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e"
 dependencies = [
  "num-traits",
 ]
@@ -142,7 +127,7 @@ version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 dependencies = [
- "hermit-abi",
+ "hermit-abi 0.1.19",
  "libc",
  "winapi",
 ]
@@ -164,9 +149,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "axum"
-version = "0.5.15"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de18bc5f2e9df8f52da03856bf40e29b747de5a84e43aefff90e3dc4a21529b"
+checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48"
 dependencies = [
  "async-trait",
  "axum-core",
@@ -183,8 +168,10 @@ dependencies = [
  "mime",
  "percent-encoding",
  "pin-project-lite",
+ "rustversion",
  "serde",
  "serde_json",
+ "serde_path_to_error",
  "serde_urlencoded",
  "sync_wrapper",
  "tokio",
@@ -196,9 +183,9 @@ dependencies = [
 
 [[package]]
 name = "axum-core"
-version = "0.2.8"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b"
+checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92"
 dependencies = [
  "async-trait",
  "bytes",
@@ -206,6 +193,7 @@ dependencies = [
  "http",
  "http-body",
  "mime",
+ "rustversion",
  "tower-layer",
  "tower-service",
 ]
@@ -229,12 +217,6 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a17bd29f7c70f32e9387f4d4acfa5ea7b7749ef784fb78cf382df97069337b8c"
 
-[[package]]
-name = "base-x"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
-
 [[package]]
 name = "base16ct"
 version = "0.1.1"
@@ -243,9 +225,9 @@ checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
 
 [[package]]
 name = "base64"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64ct"
@@ -300,11 +282,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "blake2"
-version = "0.10.4"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388"
+checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e"
 dependencies = [
- "digest 0.10.3",
+ "digest 0.10.6",
 ]
 
 [[package]]
@@ -327,9 +309,9 @@ dependencies = [
 
 [[package]]
 name = "bumpalo"
-version = "3.11.0"
+version = "3.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
+checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
 
 [[package]]
 name = "bytecount"
@@ -345,15 +327,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes"
-version = "1.2.1"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
+checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
 
 [[package]]
 name = "cc"
-version = "1.0.73"
+version = "1.0.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
 
 [[package]]
 name = "cfg-if"
@@ -388,16 +370,16 @@ dependencies = [
 
 [[package]]
 name = "chrono"
-version = "0.4.22"
+version = "0.4.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
 dependencies = [
  "iana-time-zone",
  "js-sys",
  "num-integer",
  "num-traits",
  "serde",
- "time 0.1.44",
+ "time 0.1.45",
  "wasm-bindgen",
  "winapi",
 ]
@@ -413,9 +395,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "3.2.20"
+version = "3.2.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
+checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
 dependencies = [
  "atty",
  "bitflags",
@@ -430,14 +412,14 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.0.18"
+version = "4.0.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b"
+checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
 dependencies = [
- "atty",
  "bitflags",
- "clap_derive 4.0.18",
+ "clap_derive 4.0.21",
  "clap_lex 0.3.0",
+ "is-terminal",
  "once_cell",
  "strsim",
  "termcolor",
@@ -458,9 +440,9 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.0.18"
+version = "4.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3"
+checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
 dependencies = [
  "heck 0.4.0",
  "proc-macro-error",
@@ -499,6 +481,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
 [[package]]
 name = "combine"
 version = "4.6.6"
@@ -510,7 +502,7 @@ dependencies = [
  "memchr",
  "pin-project-lite",
  "tokio",
- "tokio-util 0.7.3",
+ "tokio-util 0.7.4",
 ]
 
 [[package]]
@@ -525,12 +517,6 @@ version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
 
-[[package]]
-name = "const_fn"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
-
 [[package]]
 name = "core-foundation"
 version = "0.9.3"
@@ -558,18 +544,18 @@ dependencies = [
 
 [[package]]
 name = "crc"
-version = "2.1.0"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23"
+checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3"
 dependencies = [
  "crc-catalog",
 ]
 
 [[package]]
 name = "crc-catalog"
-version = "1.1.1"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
+checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff"
 
 [[package]]
 name = "crc16"
@@ -589,9 +575,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-queue"
-version = "0.3.6"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
 dependencies = [
  "cfg-if",
  "crossbeam-utils",
@@ -599,12 +585,11 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.11"
+version = "0.8.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
+checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
 dependencies = [
  "cfg-if",
- "once_cell",
 ]
 
 [[package]]
@@ -656,6 +641,50 @@ version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df"
 
+[[package]]
+name = "cxx"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "data-encoding"
 version = "2.3.2"
@@ -693,9 +722,9 @@ dependencies = [
 
 [[package]]
 name = "digest"
-version = "0.10.3"
+version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
 dependencies = [
  "block-buffer 0.10.3",
  "crypto-common",
@@ -722,18 +751,18 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "discard"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
-
 [[package]]
 name = "dotenv"
 version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
 
+[[package]]
+name = "dotenvy"
+version = "0.15.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
+
 [[package]]
 name = "ecdsa"
 version = "0.13.4"
@@ -748,9 +777,9 @@ dependencies = [
 
 [[package]]
 name = "ed25519-compact"
-version = "1.0.11"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24e1f30f0312ac83726c1197abeacd91c9557f8a623e904a009ae6bc529ae8d8"
+checksum = "e18997d4604542d0736fae2c5ad6de987f0a50530cbcc14a7ce5a685328a252d"
 dependencies = [
  "ct-codecs",
  "getrandom",
@@ -792,9 +821,9 @@ dependencies = [
 
 [[package]]
 name = "enum-as-inner"
-version = "0.4.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73"
+checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
 dependencies = [
  "heck 0.4.0",
  "proc-macro2",
@@ -814,6 +843,27 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
 [[package]]
 name = "event-listener"
 version = "2.5.3"
@@ -851,9 +901,9 @@ dependencies = [
 
 [[package]]
 name = "figment"
-version = "0.10.7"
+version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e3bd154d9ae2f1bb0ada5b7eebd56529513efa5de7d2fc8c6adf33bc43260cf"
+checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9"
 dependencies = [
  "atomic",
  "parking_lot 0.12.1",
@@ -894,11 +944,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
 [[package]]
 name = "form_urlencoded"
-version = "1.0.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
 dependencies = [
- "matches",
  "percent-encoding",
 ]
 
@@ -914,9 +963,9 @@ dependencies = [
 
 [[package]]
 name = "futures"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
+checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -929,9 +978,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
+checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -939,15 +988,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
+checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
+checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -956,9 +1005,9 @@ dependencies = [
 
 [[package]]
 name = "futures-intrusive"
-version = "0.4.0"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e"
+checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5"
 dependencies = [
  "futures-core",
  "lock_api",
@@ -967,15 +1016,15 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
+checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
+checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -984,21 +1033,21 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
+checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
 
 [[package]]
 name = "futures-task"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
+checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
 
 [[package]]
 name = "futures-util"
-version = "0.3.24"
+version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
+checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -1024,9 +1073,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.7"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
 dependencies = [
  "cfg-if",
  "libc",
@@ -1046,9 +1095,9 @@ dependencies = [
 
 [[package]]
 name = "h2"
-version = "0.3.14"
+version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be"
+checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
 dependencies = [
  "bytes",
  "fnv",
@@ -1059,32 +1108,26 @@ dependencies = [
  "indexmap",
  "slab",
  "tokio",
- "tokio-util 0.7.3",
+ "tokio-util 0.7.4",
  "tracing",
 ]
 
 [[package]]
 name = "hashbrown"
-version = "0.11.2"
+version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 dependencies = [
  "ahash 0.7.6",
 ]
 
-[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
 [[package]]
 name = "hashlink"
-version = "0.7.0"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
+checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
 dependencies = [
- "hashbrown 0.11.2",
+ "hashbrown",
 ]
 
 [[package]]
@@ -1100,7 +1143,7 @@ dependencies = [
  "http",
  "httpdate",
  "mime",
- "sha1 0.10.4",
+ "sha1 0.10.5",
 ]
 
 [[package]]
@@ -1139,6 +1182,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "hex"
 version = "0.4.3"
@@ -1170,29 +1222,29 @@ version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
- "digest 0.10.3",
+ "digest 0.10.6",
 ]
 
 [[package]]
 name = "hmac-sha1-compact"
-version = "1.1.1"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d103cfecf6edf3f7d1dc7c5ab64e99488c0f8d11786e43b40873e66e8489d014"
+checksum = "05e2440a0078e20c3b68ca01234cea4219f23e64b0c0bdb1200c5550d54239bb"
 
 [[package]]
 name = "hmac-sha256"
-version = "1.1.4"
+version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd29dbba58ee5314f3ec570066d78a3f4772bf45b322efcf2ce2a43af69a4d85"
+checksum = "fc736091aacb31ddaa4cd5f6988b3c21e99913ac846b41f32538c5fae5d71bfe"
 dependencies = [
  "digest 0.9.0",
 ]
 
 [[package]]
 name = "hmac-sha512"
-version = "1.1.2"
+version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a928b002dff1780b7fa21056991d395770ab9359154b8c1724c4d0511dad0a65"
+checksum = "520c9c3f6040661669bc5c91e551b605a520c8e0a63a766a91a65adef734d151"
 dependencies = [
  "digest 0.9.0",
 ]
@@ -1250,9 +1302,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
 
 [[package]]
 name = "hyper"
-version = "0.14.20"
+version = "0.14.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
+checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1274,15 +1326,15 @@ dependencies = [
 
 [[package]]
 name = "hyper-rustls"
-version = "0.23.0"
+version = "0.23.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
+checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d"
 dependencies = [
  "http",
  "hyper",
- "rustls 0.20.6",
+ "rustls",
  "tokio",
- "tokio-rustls 0.23.4",
+ "tokio-rustls",
 ]
 
 [[package]]
@@ -1299,18 +1351,28 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.47"
+version = "0.1.53"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
+ "iana-time-zone-haiku",
  "js-sys",
- "once_cell",
  "wasm-bindgen",
  "winapi",
 ]
 
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
 [[package]]
 name = "idna"
 version = "0.2.3"
@@ -1322,6 +1384,16 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "if_chain"
 version = "1.0.2"
@@ -1330,12 +1402,12 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
 
 [[package]]
 name = "indexmap"
-version = "1.9.1"
+version = "1.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
 dependencies = [
  "autocfg 1.1.0",
- "hashbrown 0.12.3",
+ "hashbrown",
 ]
 
 [[package]]
@@ -1353,53 +1425,75 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "io-lifetimes"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
+dependencies = [
+ "libc",
+ "windows-sys 0.42.0",
+]
+
 [[package]]
 name = "ipconfig"
-version = "0.3.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98"
+checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be"
 dependencies = [
  "socket2",
  "widestring",
  "winapi",
- "winreg 0.7.0",
+ "winreg",
 ]
 
 [[package]]
 name = "ipnet"
-version = "2.5.0"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
+checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
+dependencies = [
+ "hermit-abi 0.2.6",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.42.0",
+]
 
 [[package]]
 name = "iso8601"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f21abb3d09069861499d93d41a970243a90e215df0cf763ac9a31b9b27178d0"
+checksum = "296af15e112ec6dc38c9fd3ae027b5337a75466e8eed757bd7d5cf742ea85eb6"
 dependencies = [
  "nom",
 ]
 
 [[package]]
 name = "itertools"
-version = "0.10.3"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
+checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
 
 [[package]]
 name = "js-sys"
-version = "0.3.59"
+version = "0.3.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
+checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1410,11 +1504,11 @@ version = "0.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6ca9e2b45609132ae2214d50482c03aeee78826cd6fd53a8940915b81acedf16"
 dependencies = [
- "ahash 0.8.0",
+ "ahash 0.8.2",
  "anyhow",
  "base64",
  "bytecount",
- "clap 4.0.18",
+ "clap 4.0.29",
  "fancy-regex",
  "fraction",
  "iso8601",
@@ -1428,9 +1522,9 @@ dependencies = [
  "reqwest",
  "serde",
  "serde_json",
- "time 0.3.14",
+ "time 0.3.17",
  "url",
- "uuid 1.1.2",
+ "uuid",
 ]
 
 [[package]]
@@ -1480,15 +1574,24 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.132"
+version = "0.2.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
 
 [[package]]
 name = "libm"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565"
+checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
+dependencies = [
+ "cc",
+]
 
 [[package]]
 name = "linked-hash-map"
@@ -1496,11 +1599,17 @@ version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
 
+[[package]]
+name = "linux-raw-sys"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f"
+
 [[package]]
 name = "lock_api"
-version = "0.4.8"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
 dependencies = [
  "autocfg 1.1.0",
  "scopeguard",
@@ -1547,17 +1656,17 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
 
 [[package]]
 name = "matchit"
-version = "0.5.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
+checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
 
 [[package]]
 name = "md-5"
-version = "0.10.4"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "66b48670c893079d3c2ed79114e3644b7004df1c361a4e0ad52e2e6940d07c3d"
+checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca"
 dependencies = [
- "digest 0.10.3",
+ "digest 0.10.6",
 ]
 
 [[package]]
@@ -1590,14 +1699,14 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
 [[package]]
 name = "mio"
-version = "0.8.4"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
+checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
 dependencies = [
  "libc",
  "log",
  "wasi 0.11.0+wasi-snapshot-preview1",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1608,9 +1717,9 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
 
 [[package]]
 name = "native-tls"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
 dependencies = [
  "lazy_static",
  "libc",
@@ -1634,13 +1743,23 @@ dependencies = [
  "minimal-lexical",
 ]
 
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
 [[package]]
 name = "num"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
 dependencies = [
- "num-bigint 0.4.3",
+ "num-bigint",
  "num-complex",
  "num-integer",
  "num-iter",
@@ -1648,17 +1767,6 @@ dependencies = [
  "num-traits",
 ]
 
-[[package]]
-name = "num-bigint"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3"
-dependencies = [
- "autocfg 1.1.0",
- "num-integer",
- "num-traits",
-]
-
 [[package]]
 name = "num-bigint"
 version = "0.4.3"
@@ -1731,7 +1839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
 dependencies = [
  "autocfg 1.1.0",
- "num-bigint 0.4.3",
+ "num-bigint",
  "num-integer",
  "num-traits",
 ]
@@ -1748,11 +1856,11 @@ dependencies = [
 
 [[package]]
 name = "num_cpus"
-version = "1.13.1"
+version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
 dependencies = [
- "hermit-abi",
+ "hermit-abi 0.1.19",
  "libc",
 ]
 
@@ -1777,20 +1885,11 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "num_threads"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
-dependencies = [
- "libc",
-]
-
 [[package]]
 name = "once_cell"
-version = "1.14.0"
+version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
+checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
 
 [[package]]
 name = "opaque-debug"
@@ -1800,9 +1899,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
 [[package]]
 name = "openssl"
-version = "0.10.41"
+version = "0.10.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
+checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376"
 dependencies = [
  "bitflags",
  "cfg-if",
@@ -1832,9 +1931,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.75"
+version = "0.9.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
+checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132"
 dependencies = [
  "autocfg 1.1.0",
  "cc",
@@ -1896,15 +1995,15 @@ dependencies = [
 
 [[package]]
 name = "os_str_bytes"
-version = "6.3.0"
+version = "6.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
+checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
 
 [[package]]
 name = "ouroboros"
-version = "0.15.4"
+version = "0.15.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f56a2b0aa5fc88687aaf63e85a7974422790ce3419a2e1a15870f8a55227822"
+checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca"
 dependencies = [
  "aliasable",
  "ouroboros_macro",
@@ -1912,9 +2011,9 @@ dependencies = [
 
 [[package]]
 name = "ouroboros_macro"
-version = "0.15.4"
+version = "0.15.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c40641e27d0eb38cae3dee081d920104d2db47a8e853c1a592ef68d33f5ebf4"
+checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d"
 dependencies = [
  "Inflector",
  "proc-macro-error",
@@ -1923,6 +2022,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
 [[package]]
 name = "p256"
 version = "0.10.1"
@@ -1953,7 +2058,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
 dependencies = [
  "lock_api",
- "parking_lot_core 0.9.3",
+ "parking_lot_core 0.9.5",
 ]
 
 [[package]]
@@ -1972,15 +2077,15 @@ dependencies = [
 
 [[package]]
 name = "parking_lot_core"
-version = "0.9.3"
+version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
+checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
 dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -2032,9 +2137,9 @@ dependencies = [
 
 [[package]]
 name = "percent-encoding"
-version = "2.1.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
 
 [[package]]
 name = "petgraph"
@@ -2115,9 +2220,9 @@ dependencies = [
 
 [[package]]
 name = "pkg-config"
-version = "0.3.25"
+version = "0.3.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
 
 [[package]]
 name = "poly1305"
@@ -2132,9 +2237,9 @@ dependencies = [
 
 [[package]]
 name = "ppv-lite86"
-version = "0.2.16"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "proc-macro-crate"
@@ -2171,17 +2276,11 @@ dependencies = [
  "version_check",
 ]
 
-[[package]]
-name = "proc-macro-hack"
-version = "0.5.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
-
 [[package]]
 name = "proc-macro2"
-version = "1.0.43"
+version = "1.0.47"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
 dependencies = [
  "unicode-ident",
 ]
@@ -2290,9 +2389,9 @@ dependencies = [
 
 [[package]]
 name = "rand_core"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
  "getrandom",
 ]
@@ -2315,7 +2414,7 @@ dependencies = [
  "sha1 0.6.1",
  "tokio",
  "tokio-native-tls",
- "tokio-util 0.7.3",
+ "tokio-util 0.7.4",
  "url",
 ]
 
@@ -2355,9 +2454,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.6.0"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2375,9 +2474,9 @@ dependencies = [
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.27"
+version = "0.6.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
 
 [[package]]
 name = "remove_dir_all"
@@ -2390,9 +2489,9 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.11.11"
+version = "0.11.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92"
+checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
 dependencies = [
  "base64",
  "bytes",
@@ -2406,27 +2505,27 @@ dependencies = [
  "hyper-rustls",
  "ipnet",
  "js-sys",
- "lazy_static",
  "log",
  "mime",
  "mime_guess",
+ "once_cell",
  "percent-encoding",
  "pin-project-lite",
- "rustls 0.20.6",
+ "rustls",
  "rustls-pemfile",
  "serde",
  "serde_json",
  "serde_urlencoded",
  "tokio",
- "tokio-rustls 0.23.4",
+ "tokio-rustls",
  "tower-service",
  "trust-dns-resolver",
  "url",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "web-sys",
- "webpki-roots 0.22.4",
- "winreg 0.10.1",
+ "webpki-roots",
+ "winreg",
 ]
 
 [[package]]
@@ -2486,48 +2585,29 @@ dependencies = [
 ]
 
 [[package]]
-name = "rust_decimal"
-version = "1.26.1"
+name = "rustix"
+version = "0.36.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c"
+checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23"
 dependencies = [
- "arrayvec",
- "num-traits",
- "serde",
-]
-
-[[package]]
-name = "rustc_version"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-dependencies = [
- "semver",
-]
-
-[[package]]
-name = "rustls"
-version = "0.19.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
-dependencies = [
- "base64",
- "log",
- "ring",
- "sct 0.6.1",
- "webpki 0.21.4",
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
 name = "rustls"
-version = "0.20.6"
+version = "0.20.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
+checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
 dependencies = [
  "log",
  "ring",
- "sct 0.7.0",
- "webpki 0.22.0",
+ "sct",
+ "webpki",
 ]
 
 [[package]]
@@ -2558,7 +2638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
 dependencies = [
  "lazy_static",
- "windows-sys",
+ "windows-sys 0.36.1",
 ]
 
 [[package]]
@@ -2568,14 +2648,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
-name = "sct"
-version = "0.6.1"
+name = "scratch"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
-dependencies = [
- "ring",
- "untrusted",
-]
+checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
 
 [[package]]
 name = "sct"
@@ -2589,35 +2665,35 @@ dependencies = [
 
 [[package]]
 name = "sea-orm"
-version = "0.7.1"
+version = "0.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27dbb8a742003f8dbf2ba290d128134d4275a6b55fd02f4d728683b6b55ea9bf"
+checksum = "3120bc435b8640963ffda698f877610e07e077157e216eb99408d819c344034d"
 dependencies = [
  "async-stream",
  "async-trait",
  "chrono",
  "futures",
  "futures-util",
- "once_cell",
+ "log",
  "ouroboros",
- "rust_decimal",
  "sea-orm-macros",
  "sea-query",
+ "sea-query-binder",
  "sea-strum",
  "serde",
  "serde_json",
  "sqlx",
- "time 0.2.27",
+ "thiserror",
+ "time 0.3.17",
  "tracing",
  "url",
- "uuid 0.8.2",
 ]
 
 [[package]]
 name = "sea-orm-macros"
-version = "0.7.0"
+version = "0.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953bf5fb9f6ec985c139c4a98550b600c2f7c97bea74e2acc4025438469cb5a2"
+checksum = "c54bacfeb842813c16821e21f9456c358861a448294075184ea1d6307e386d08"
 dependencies = [
  "bae",
  "heck 0.3.3",
@@ -2628,16 +2704,25 @@ dependencies = [
 
 [[package]]
 name = "sea-query"
-version = "0.23.0"
+version = "0.27.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bf24fc03259e206d8cd4c957ce7446fe54ab00ba5ada4cdb028aa3513e26231"
+checksum = "a4f0fc4d8e44e1d51c739a68d336252a18bc59553778075d5e32649be6ec92ed"
 dependencies = [
  "chrono",
- "rust_decimal",
  "sea-query-derive",
  "serde_json",
- "time 0.2.27",
- "uuid 0.8.2",
+]
+
+[[package]]
+name = "sea-query-binder"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c2585b89c985cfacfe0ec9fc9e7bb055b776c1a2581c4e3c6185af2b8bf8865"
+dependencies = [
+ "chrono",
+ "sea-query",
+ "serde_json",
+ "sqlx",
 ]
 
 [[package]]
@@ -2711,35 +2796,20 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "semver"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-dependencies = [
- "semver-parser",
-]
-
-[[package]]
-name = "semver-parser"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-
 [[package]]
 name = "serde"
-version = "1.0.144"
+version = "1.0.148"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
+checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.144"
+version = "1.0.148"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
+checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2748,9 +2818,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.85"
+version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
+checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
 dependencies = [
  "itoa",
  "ryu",
@@ -2778,17 +2848,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "sha-1"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest 0.10.3",
-]
-
 [[package]]
 name = "sha1"
 version = "0.6.1"
@@ -2800,13 +2859,13 @@ dependencies = [
 
 [[package]]
 name = "sha1"
-version = "0.10.4"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549"
+checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest 0.10.3",
+ "digest 0.10.6",
 ]
 
 [[package]]
@@ -2830,13 +2889,13 @@ dependencies = [
 
 [[package]]
 name = "sha2"
-version = "0.10.5"
+version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest 0.10.3",
+ "digest 0.10.6",
 ]
 
 [[package]]
@@ -2878,9 +2937,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.9.0"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
 
 [[package]]
 name = "socket2"
@@ -2919,9 +2978,9 @@ dependencies = [
 
 [[package]]
 name = "sqlformat"
-version = "0.1.8"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4"
+checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a"
 dependencies = [
  "itertools",
  "nom",
@@ -2930,9 +2989,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx"
-version = "0.5.13"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b"
+checksum = "9249290c05928352f71c077cc44a464d880c63f26f7534728cca008e135c0428"
 dependencies = [
  "sqlx-core",
  "sqlx-macros",
@@ -2940,9 +2999,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx-core"
-version = "0.5.13"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5"
+checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105"
 dependencies = [
  "ahash 0.7.6",
  "atoi",
@@ -2954,6 +3013,7 @@ dependencies = [
  "crc",
  "crossbeam-queue",
  "dirs",
+ "dotenvy",
  "either",
  "event-listener",
  "futures-channel",
@@ -2970,45 +3030,41 @@ dependencies = [
  "log",
  "md-5",
  "memchr",
- "num-bigint 0.3.3",
  "once_cell",
  "paste",
  "percent-encoding",
  "rand",
- "rust_decimal",
- "rustls 0.19.1",
+ "rustls",
+ "rustls-pemfile",
  "serde",
  "serde_json",
- "sha-1",
- "sha2 0.10.5",
+ "sha1 0.10.5",
+ "sha2 0.10.6",
  "smallvec",
  "sqlformat",
  "sqlx-rt",
  "stringprep",
  "thiserror",
- "time 0.2.27",
  "tokio-stream",
  "url",
- "uuid 0.8.2",
- "webpki 0.21.4",
- "webpki-roots 0.21.1",
+ "webpki-roots",
  "whoami",
 ]
 
 [[package]]
 name = "sqlx-macros"
-version = "0.5.13"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1"
+checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9"
 dependencies = [
- "dotenv",
+ "dotenvy",
  "either",
  "heck 0.4.0",
  "once_cell",
  "proc-macro2",
  "quote",
  "serde_json",
- "sha2 0.10.5",
+ "sha2 0.10.6",
  "sqlx-core",
  "sqlx-rt",
  "syn",
@@ -3017,73 +3073,15 @@ dependencies = [
 
 [[package]]
 name = "sqlx-rt"
-version = "0.5.13"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae"
+checksum = "24c5b2d25fa654cc5f841750b8e1cdedbe21189bf9a9382ee90bfa9dd3562396"
 dependencies = [
  "once_cell",
  "tokio",
- "tokio-rustls 0.22.0",
-]
-
-[[package]]
-name = "standback"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
-dependencies = [
- "version_check",
-]
-
-[[package]]
-name = "stdweb"
-version = "0.4.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
-dependencies = [
- "discard",
- "rustc_version",
- "stdweb-derive",
- "stdweb-internal-macros",
- "stdweb-internal-runtime",
- "wasm-bindgen",
+ "tokio-rustls",
 ]
 
-[[package]]
-name = "stdweb-derive"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
-dependencies = [
- "proc-macro2",
- "quote",
- "serde",
- "serde_derive",
- "syn",
-]
-
-[[package]]
-name = "stdweb-internal-macros"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
-dependencies = [
- "base-x",
- "proc-macro2",
- "quote",
- "serde",
- "serde_derive",
- "serde_json",
- "sha1 0.6.1",
- "syn",
-]
-
-[[package]]
-name = "stdweb-internal-runtime"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
-
 [[package]]
 name = "stringprep"
 version = "0.1.2"
@@ -3120,7 +3118,7 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "thiserror",
- "time 0.3.14",
+ "time 0.3.17",
  "url",
 ]
 
@@ -3138,7 +3136,7 @@ dependencies = [
 
 [[package]]
 name = "svix-server"
-version = "0.69.0"
+version = "0.71.0"
 dependencies = [
  "anyhow",
  "axum",
@@ -3149,7 +3147,7 @@ dependencies = [
  "bytes",
  "chacha20poly1305",
  "chrono",
- "clap 3.2.20",
+ "clap 3.2.23",
  "dotenv",
  "ed25519-compact",
  "enum_dispatch",
@@ -3176,13 +3174,13 @@ dependencies = [
  "serde_json",
  "serde_path_to_error",
  "serde_urlencoded",
- "sha2 0.10.5",
+ "sha2 0.10.6",
  "sqlx",
  "svix",
  "svix-ksuid",
  "svix-server_derive",
  "thiserror",
- "time 0.3.14",
+ "time 0.3.17",
  "tokio",
  "tower",
  "tower-http",
@@ -3204,9 +3202,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.99"
+version = "1.0.104"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
+checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3256,24 +3254,24 @@ dependencies = [
 
 [[package]]
 name = "textwrap"
-version = "0.15.0"
+version = "0.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
 
 [[package]]
 name = "thiserror"
-version = "1.0.34"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.34"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3291,9 +3289,9 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.1.44"
+version = "0.1.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
 dependencies = [
  "libc",
  "wasi 0.10.0+wasi-snapshot-preview1",
@@ -3302,57 +3300,28 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.2.27"
+version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
+checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
 dependencies = [
- "const_fn",
- "libc",
- "standback",
- "stdweb",
- "time-macros 0.1.1",
- "version_check",
- "winapi",
-]
-
-[[package]]
-name = "time"
-version = "0.3.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b"
-dependencies = [
- "libc",
- "num_threads",
- "time-macros 0.2.4",
+ "serde",
+ "time-core",
+ "time-macros",
 ]
 
 [[package]]
-name = "time-macros"
-version = "0.1.1"
+name = "time-core"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
-dependencies = [
- "proc-macro-hack",
- "time-macros-impl",
-]
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
 
 [[package]]
 name = "time-macros"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
-
-[[package]]
-name = "time-macros-impl"
-version = "0.1.2"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
+checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
 dependencies = [
- "proc-macro-hack",
- "proc-macro2",
- "quote",
- "standback",
- "syn",
+ "time-core",
 ]
 
 [[package]]
@@ -3372,9 +3341,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.21.0"
+version = "1.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
+checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3"
 dependencies = [
  "autocfg 1.1.0",
  "bytes",
@@ -3382,7 +3351,6 @@ dependencies = [
  "memchr",
  "mio",
  "num_cpus",
- "once_cell",
  "parking_lot 0.12.1",
  "pin-project-lite",
  "signal-hook-registry",
@@ -3422,33 +3390,22 @@ dependencies = [
  "tokio",
 ]
 
-[[package]]
-name = "tokio-rustls"
-version = "0.22.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
-dependencies = [
- "rustls 0.19.1",
- "tokio",
- "webpki 0.21.4",
-]
-
 [[package]]
 name = "tokio-rustls"
 version = "0.23.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
 dependencies = [
- "rustls 0.20.6",
+ "rustls",
  "tokio",
- "webpki 0.22.0",
+ "webpki",
 ]
 
 [[package]]
 name = "tokio-stream"
-version = "0.1.9"
+version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9"
+checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
 dependencies = [
  "futures-core",
  "pin-project-lite",
@@ -3471,9 +3428,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.7.3"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
+checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
 dependencies = [
  "bytes",
  "futures-core",
@@ -3549,7 +3506,7 @@ dependencies = [
  "rand",
  "slab",
  "tokio",
- "tokio-util 0.7.3",
+ "tokio-util 0.7.4",
  "tower-layer",
  "tower-service",
  "tracing",
@@ -3573,14 +3530,14 @@ dependencies = [
  "tower-layer",
  "tower-service",
  "tracing",
- "uuid 1.1.2",
+ "uuid",
 ]
 
 [[package]]
 name = "tower-layer"
-version = "0.3.1"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
 
 [[package]]
 name = "tower-service"
@@ -3590,9 +3547,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
 
 [[package]]
 name = "tracing"
-version = "0.1.36"
+version = "0.1.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
 dependencies = [
  "cfg-if",
  "log",
@@ -3603,9 +3560,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-attributes"
-version = "0.1.22"
+version = "0.1.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3614,9 +3571,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.29"
+version = "0.1.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
 dependencies = [
  "once_cell",
  "valuable",
@@ -3669,12 +3626,12 @@ dependencies = [
 
 [[package]]
 name = "tracing-subscriber"
-version = "0.3.15"
+version = "0.3.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
 dependencies = [
- "ansi_term",
  "matchers",
+ "nu-ansi-term",
  "once_cell",
  "regex",
  "serde",
@@ -3690,9 +3647,9 @@ dependencies = [
 
 [[package]]
 name = "trust-dns-proto"
-version = "0.21.2"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d"
+checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26"
 dependencies = [
  "async-trait",
  "cfg-if",
@@ -3701,35 +3658,35 @@ dependencies = [
  "futures-channel",
  "futures-io",
  "futures-util",
- "idna",
+ "idna 0.2.3",
  "ipnet",
  "lazy_static",
- "log",
  "rand",
  "smallvec",
  "thiserror",
  "tinyvec",
  "tokio",
+ "tracing",
  "url",
 ]
 
 [[package]]
 name = "trust-dns-resolver"
-version = "0.21.2"
+version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558"
+checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe"
 dependencies = [
  "cfg-if",
  "futures-util",
  "ipconfig",
  "lazy_static",
- "log",
  "lru-cache",
  "parking_lot 0.12.1",
  "resolv-conf",
  "smallvec",
  "thiserror",
  "tokio",
+ "tracing",
  "trust-dns-proto",
 ]
 
@@ -3771,30 +3728,36 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.3"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
 
 [[package]]
 name = "unicode-normalization"
-version = "0.1.21"
+version = "0.1.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
 dependencies = [
  "tinyvec",
 ]
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.9.0"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
 
 [[package]]
 name = "unicode_categories"
@@ -3820,31 +3783,20 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
 
 [[package]]
 name = "url"
-version = "2.2.2"
+version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
 dependencies = [
  "form_urlencoded",
- "idna",
- "matches",
+ "idna 0.3.0",
  "percent-encoding",
 ]
 
 [[package]]
 name = "uuid"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
-dependencies = [
- "getrandom",
- "serde",
-]
-
-[[package]]
-name = "uuid"
-version = "1.1.2"
+version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
+checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
 dependencies = [
  "getrandom",
 ]
@@ -3855,7 +3807,7 @@ version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6d0f08911ab0fee2c5009580f04615fa868898ee57de10692a45da0c3bcc3e5e"
 dependencies = [
- "idna",
+ "idna 0.2.3",
  "lazy_static",
  "regex",
  "serde",
@@ -3934,9 +3886,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.82"
+version = "0.2.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
+checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -3944,9 +3896,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.82"
+version = "0.2.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
+checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
 dependencies = [
  "bumpalo",
  "log",
@@ -3959,9 +3911,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.32"
+version = "0.4.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad"
+checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -3971,9 +3923,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.82"
+version = "0.2.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
+checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3981,9 +3933,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.82"
+version = "0.2.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
+checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3994,30 +3946,20 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.82"
+version = "0.2.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
+checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
 
 [[package]]
 name = "web-sys"
-version = "0.3.59"
+version = "0.3.60"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1"
+checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "webpki"
-version = "0.21.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
-dependencies = [
- "ring",
- "untrusted",
-]
-
 [[package]]
 name = "webpki"
 version = "0.22.0"
@@ -4030,20 +3972,11 @@ dependencies = [
 
 [[package]]
 name = "webpki-roots"
-version = "0.21.1"
+version = "0.22.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
+checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
 dependencies = [
- "webpki 0.21.4",
-]
-
-[[package]]
-name = "webpki-roots"
-version = "0.22.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
-dependencies = [
- "webpki 0.22.0",
+ "webpki",
 ]
 
 [[package]]
@@ -4059,10 +3992,11 @@ dependencies = [
 
 [[package]]
 name = "whoami"
-version = "1.2.1"
+version = "1.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8"
+checksum = "d6631b6a2fd59b1841b622e8f1a7ad241ef0a46f2d580464ce8140ac94cbd571"
 dependencies = [
+ "bumpalo",
  "wasm-bindgen",
  "web-sys",
 ]
@@ -4110,37 +4044,88 @@ version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
 dependencies = [
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_msvc",
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc 0.42.0",
+ "windows_i686_gnu 0.42.0",
+ "windows_i686_msvc 0.42.0",
+ "windows_x86_64_gnu 0.42.0",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc 0.42.0",
 ]
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.36.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.36.1"
@@ -4148,13 +4133,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
 
 [[package]]
-name = "winreg"
-version = "0.7.0"
+name = "windows_x86_64_msvc"
+version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
-dependencies = [
- "winapi",
-]
+checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
 
 [[package]]
 name = "winreg"
diff --git a/server/Dockerfile b/server/Dockerfile
index eb3030647..54aa42ae3 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -1,5 +1,5 @@
 # Base build
-FROM rust:1.63-slim-bullseye AS build
+FROM rust:1.65-slim-bullseye AS build
 
 RUN apt-get update && apt-get install -y build-essential=12.* checkinstall=1.* zlib1g-dev=1:* pkg-config=0.29.* libssl-dev=* --no-install-recommends
 
diff --git a/server/docker-compose.yml b/server/docker-compose.yml
index 5bb223abc..d53968e88 100644
--- a/server/docker-compose.yml
+++ b/server/docker-compose.yml
@@ -11,6 +11,10 @@ services:
       SVIX_DB_DSN: "postgresql://postgres:postgres@pgbouncer/postgres"
     ports:
       - "8071:8071"
+    depends_on:
+    - pgbouncer
+    - redis
+
   postgres:
     image: postgres:13.4
     volumes:
@@ -30,6 +34,9 @@ services:
       DB_USER: "postgres"
       DB_PASSWORD: "postgres"
       MAX_CLIENT_CONN: 500
+    depends_on:
+    - postgres
+
   redis:
     image: "redis:6.2-alpine"
     # Enable persistence
diff --git a/server/svix-server/Cargo.toml b/server/svix-server/Cargo.toml
index bd6eb29bd..9ed52db3a 100644
--- a/server/svix-server/Cargo.toml
+++ b/server/svix-server/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "svix-server"
-version = "0.69.0"
+version = "0.71.0"
 license = "MIT"
 description = "Svix webhooks server"
 
@@ -20,7 +20,7 @@ dotenv = "0.15.0"
 sha2 = "0.10.2"
 hmac-sha256 = "1"
 clap = { version = "3.2.1", features = ["derive"] }
-axum = { version = "0.5.1", features = ["headers"] }
+axum = { version = "0.6.1", features = ["headers"] }
 base64 = "0.13.0"
 hyper = { version = "0.14.16", features = ["full"] }
 tokio = { version = "1.15.0", features = ["full"] }
@@ -55,8 +55,8 @@ bytes = "1.1.0"
 blake2 = "0.10.4"
 chacha20poly1305 = "0.9.0"
 # sea orm
-sea-orm = { version = "0.7.1", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros" ], default-features = false }
-sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "postgres", "migrate" ] }
+sea-orm = { version = "0.10.2", features = [ "sqlx-postgres", "runtime-tokio-rustls", "macros", "with-chrono", "with-json" ], default-features = false }
+sqlx = { version = "0.6.2", features = [ "runtime-tokio-rustls", "postgres", "migrate" ] }
 http = "0.2"
 time = { version = "0.3.9", features = [ "std" ]}
 futures = "0.3"
diff --git a/server/svix-server/config.default.toml b/server/svix-server/config.default.toml
index 24fcaeea9..dd488648a 100644
--- a/server/svix-server/config.default.toml
+++ b/server/svix-server/config.default.toml
@@ -35,7 +35,7 @@ log_format = "default"
 # always sending. If the OpenTelemetry address is not set, this will do nothing.
 # opentelemetry_sample_ratio = 1.0
 
-# Whether to enable the logging of the databses at the configured log level. This may be useful for
+# Whether to enable the logging of the databases at the configured log level. This may be useful for
 # analyzing their response times.
 db_tracing = false
 
diff --git a/server/svix-server/migrations/20221104170041_add_applicationmetadata.down.sql b/server/svix-server/migrations/20221104170041_add_applicationmetadata.down.sql
new file mode 100644
index 000000000..1af82808b
--- /dev/null
+++ b/server/svix-server/migrations/20221104170041_add_applicationmetadata.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+DROP TABLE IF EXISTS applicationmetadata;
\ No newline at end of file
diff --git a/server/svix-server/migrations/20221104170041_add_applicationmetadata.up.sql b/server/svix-server/migrations/20221104170041_add_applicationmetadata.up.sql
new file mode 100644
index 000000000..eff04a0ab
--- /dev/null
+++ b/server/svix-server/migrations/20221104170041_add_applicationmetadata.up.sql
@@ -0,0 +1,10 @@
+-- Add up migration script here
+CREATE TABLE applicationmetadata (
+    created_at timestamp with time zone NOT NULL,
+    updated_at timestamp with time zone NOT NULL,
+    id character varying NOT NULL COLLATE pg_catalog."C",
+    data jsonb NOT NULL
+);
+
+ALTER TABLE ONLY applicationmetadata
+    ADD CONSTRAINT applicationmetadata_pkey PRIMARY KEY (id);
\ No newline at end of file
diff --git a/server/svix-server/migrations/20221129215059_correct_collation.down.sql b/server/svix-server/migrations/20221129215059_correct_collation.down.sql
new file mode 100644
index 000000000..395479629
--- /dev/null
+++ b/server/svix-server/migrations/20221129215059_correct_collation.down.sql
@@ -0,0 +1,3 @@
+ALTER TABLE eventtype ALTER name TYPE VARCHAR;
+ALTER TABLE message ALTER event_type TYPE VARCHAR;
+ALTER TABLE message ALTER uid TYPE VARCHAR;
diff --git a/server/svix-server/migrations/20221129215059_correct_collation.up.sql b/server/svix-server/migrations/20221129215059_correct_collation.up.sql
new file mode 100644
index 000000000..80d1a8e5e
--- /dev/null
+++ b/server/svix-server/migrations/20221129215059_correct_collation.up.sql
@@ -0,0 +1,3 @@
+ALTER TABLE eventtype ALTER name TYPE VARCHAR COLLATE "C";
+ALTER TABLE message ALTER event_type TYPE VARCHAR COLLATE "C";
+ALTER TABLE message ALTER uid TYPE VARCHAR COLLATE "C";
diff --git a/server/svix-server/migrations/20221206214656_missing_msg_dest_endpoint.down.sql b/server/svix-server/migrations/20221206214656_missing_msg_dest_endpoint.down.sql
new file mode 100644
index 000000000..8daede113
--- /dev/null
+++ b/server/svix-server/migrations/20221206214656_missing_msg_dest_endpoint.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+DROP INDEX ix_messagedestination_per_msg_no_status;
diff --git a/server/svix-server/migrations/20221206214656_missing_msg_dest_endpoint.up.sql b/server/svix-server/migrations/20221206214656_missing_msg_dest_endpoint.up.sql
new file mode 100644
index 000000000..e7ce6869d
--- /dev/null
+++ b/server/svix-server/migrations/20221206214656_missing_msg_dest_endpoint.up.sql
@@ -0,0 +1,2 @@
+-- Add up migration script here
+CREATE INDEX ix_messagedestination_per_msg_no_status ON messagedestination USING btree (msg_id);
diff --git a/server/svix-server/migrations/20221215195541_add_endpoint_metadata.down.sql b/server/svix-server/migrations/20221215195541_add_endpoint_metadata.down.sql
new file mode 100644
index 000000000..45cc38cf4
--- /dev/null
+++ b/server/svix-server/migrations/20221215195541_add_endpoint_metadata.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+DROP TABLE IF EXISTS endpointmetadata;
\ No newline at end of file
diff --git a/server/svix-server/migrations/20221215195541_add_endpoint_metadata.up.sql b/server/svix-server/migrations/20221215195541_add_endpoint_metadata.up.sql
new file mode 100644
index 000000000..624e45a71
--- /dev/null
+++ b/server/svix-server/migrations/20221215195541_add_endpoint_metadata.up.sql
@@ -0,0 +1,10 @@
+-- Add up migration script here
+CREATE TABLE endpointmetadata (
+    created_at timestamp with time zone NOT NULL,
+    updated_at timestamp with time zone NOT NULL,
+    id character varying NOT NULL COLLATE pg_catalog."C",
+    data jsonb NOT NULL
+);
+
+ALTER TABLE ONLY endpointmetadata 
+    ADD CONSTRAINT endpointmetadata_pkey PRIMARY KEY (id);
\ No newline at end of file
diff --git a/server/svix-server/src/core/cache/memory.rs b/server/svix-server/src/core/cache/memory.rs
index 24fbb1803..888c81f8d 100644
--- a/server/svix-server/src/core/cache/memory.rs
+++ b/server/svix-server/src/core/cache/memory.rs
@@ -119,7 +119,7 @@ mod tests {
     kv_def!(TestKeyA, TestValA);
     impl TestKeyA {
         fn new(id: String) -> TestKeyA {
-            TestKeyA(format!("SVIX_TEST_KEY_A_{}", id))
+            TestKeyA(format!("SVIX_TEST_KEY_A_{id}"))
         }
     }
 
@@ -128,7 +128,7 @@ mod tests {
     kv_def!(TestKeyB, TestValB);
     impl TestKeyB {
         fn new(id: String) -> TestKeyB {
-            TestKeyB(format!("SVIX_TEST_KEY_B_{}", id))
+            TestKeyB(format!("SVIX_TEST_KEY_B_{id}"))
         }
     }
 
@@ -137,7 +137,7 @@ mod tests {
     string_kv_def!(StringTestKey, StringTestVal);
     impl StringTestKey {
         fn new(id: String) -> StringTestKey {
-            StringTestKey(format!("SVIX_TEST_KEY_STRING_{}", id))
+            StringTestKey(format!("SVIX_TEST_KEY_STRING_{id}"))
         }
     }
 
diff --git a/server/svix-server/src/core/cache/mod.rs b/server/svix-server/src/core/cache/mod.rs
index 7ad601450..6d438ce42 100644
--- a/server/svix-server/src/core/cache/mod.rs
+++ b/server/svix-server/src/core/cache/mod.rs
@@ -29,7 +29,7 @@ pub enum Error {
     #[error("Redis pool error")]
     Pool(#[from] bb8::RunError<RedisError>),
 
-    #[error("Redis databse error")]
+    #[error("Redis database error")]
     Database(#[from] RedisError),
 
     #[error("input error: {0}")]
diff --git a/server/svix-server/src/core/cache/redis.rs b/server/svix-server/src/core/cache/redis.rs
index 03f35ac9a..64108b324 100644
--- a/server/svix-server/src/core/cache/redis.rs
+++ b/server/svix-server/src/core/cache/redis.rs
@@ -39,10 +39,7 @@ impl CacheBehavior for RedisCache {
             key,
             value,
             ttl.as_millis().try_into().map_err(|e| {
-                Error::Input(format!(
-                    "Duration given cannot be converted to usize: {}",
-                    e
-                ))
+                Error::Input(format!("Duration given cannot be converted to usize: {e}"))
             })?,
         )
         .await
@@ -56,10 +53,7 @@ impl CacheBehavior for RedisCache {
 
         cmd.arg("PX");
         let ttl_as_millis: u64 = ttl.as_millis().try_into().map_err(|e| {
-            Error::Input(format!(
-                "Duration given cannot be converted to usize: {}",
-                e
-            ))
+            Error::Input(format!("Duration given cannot be converted to usize: {e}"))
         })?;
         cmd.arg(ttl_as_millis);
 
@@ -96,7 +90,7 @@ mod tests {
     kv_def!(TestKeyA, TestValA);
     impl TestKeyA {
         fn new(id: String) -> TestKeyA {
-            TestKeyA(format!("SVIX_TEST_KEY_A_{}", id))
+            TestKeyA(format!("SVIX_TEST_KEY_A_{id}"))
         }
     }
 
@@ -105,7 +99,7 @@ mod tests {
     kv_def!(TestKeyB, TestValB);
     impl TestKeyB {
         fn new(id: String) -> TestKeyB {
-            TestKeyB(format!("SVIX_TEST_KEY_B_{}", id))
+            TestKeyB(format!("SVIX_TEST_KEY_B_{id}"))
         }
     }
 
@@ -114,7 +108,7 @@ mod tests {
     string_kv_def!(StringTestKey, StringTestVal);
     impl StringTestKey {
         fn new(id: String) -> StringTestKey {
-            StringTestKey(format!("SVIX_TEST_KEY_STRING_{}", id))
+            StringTestKey(format!("SVIX_TEST_KEY_STRING_{id}"))
         }
     }
 
diff --git a/server/svix-server/src/core/idempotency.rs b/server/svix-server/src/core/idempotency.rs
index d33232883..8aaa25756 100644
--- a/server/svix-server/src/core/idempotency.rs
+++ b/server/svix-server/src/core/idempotency.rs
@@ -390,12 +390,12 @@ mod tests {
                     .serve(
                         Router::new()
                             .route("/", post(service_endpoint).get(service_endpoint))
-                            .layer(
-                                ServiceBuilder::new().layer_fn(|service| IdempotencyService {
+                            .layer(ServiceBuilder::new().layer_fn(move |service| {
+                                IdempotencyService {
                                     cache: cache.clone(),
                                     service,
-                                }),
-                            )
+                                }
+                            }))
                             .layer(Extension(count))
                             .layer(Extension(wait))
                             .into_make_service(),
@@ -420,7 +420,7 @@ mod tests {
             tokio::time::sleep(wait).await;
         }
 
-        format!("{}", count)
+        format!("{count}")
     }
 
     #[tokio::test]
@@ -591,12 +591,12 @@ mod tests {
                     .serve(
                         Router::new()
                             .route("/", post(empty_service_endpoint))
-                            .layer(
-                                ServiceBuilder::new().layer_fn(|service| IdempotencyService {
+                            .layer(ServiceBuilder::new().layer_fn(move |service| {
+                                IdempotencyService {
                                     cache: cache.clone(),
                                     service,
-                                }),
-                            )
+                                }
+                            }))
                             .layer(Extension(count))
                             .into_make_service(),
                     )
diff --git a/server/svix-server/src/core/message_app.rs b/server/svix-server/src/core/message_app.rs
index 52be5a063..d0fc6bc6f 100644
--- a/server/svix-server/src/core/message_app.rs
+++ b/server/svix-server/src/core/message_app.rs
@@ -18,11 +18,13 @@ use crate::{
         },
     },
     ctx,
-    db::models::{application, endpoint, message},
+    db::models::{application, endpoint},
     err_validation,
     error::{Error, Result},
 };
 
+use super::types::EventTypeName;
+
 /// The information cached during the creation of a message. Includes a [`Vec`] of all endpoints
 /// associated with the given application and organization ID.
 #[derive(Deserialize, Serialize, Debug, Clone)]
@@ -68,11 +70,11 @@ impl CreateMessageApp {
         cache: Cache,
         pg: &DatabaseConnection,
         app: Option<application::Model>,
-        app_id: ApplicationId,
         org_id: OrganizationId,
+        app_id: ApplicationId,
         ttl: Duration,
     ) -> Result<Option<CreateMessageApp>> {
-        let cache_key = AppEndpointKey::new(org_id.clone(), app_id.clone());
+        let cache_key = AppEndpointKey::new(&org_id, &app_id);
 
         // First check Redis
         if let Ok(Some(cma)) = cache.get(&cache_key).await {
@@ -106,7 +108,8 @@ impl CreateMessageApp {
     pub fn filtered_endpoints(
         &self,
         trigger_type: MessageAttemptTriggerType,
-        msg: &message::Model,
+        event_type: &EventTypeName,
+        channels: Option<&EventChannelSet>,
     ) -> Vec<CreateMessageEndpoint> {
         self
         .endpoints
@@ -123,7 +126,7 @@ impl CreateMessageApp {
                         endpoint
                         .event_types_ids
                         .as_ref()
-                        .map(|x| x.0.contains(&msg.event_type))
+                        .map(|x| x.0.contains(event_type))
                         .unwrap_or(true)
                     &&
                         // If an endpoint has no channels accept all messages, otherwise only if their channels overlap.
@@ -131,7 +134,7 @@ impl CreateMessageApp {
                         endpoint
                         .channels
                         .as_ref()
-                        .map(|x| !x.0.is_disjoint(msg.channels.as_ref().map(|x| &x.0).unwrap_or(&HashSet::new())))
+                        .map(|x| !x.0.is_disjoint(channels.map(|x| &x.0).unwrap_or(&HashSet::new())))
                         .unwrap_or(true)
             ))})
         .cloned()
@@ -201,7 +204,7 @@ kv_def!(AppEndpointKey, CreateMessageApp);
 impl AppEndpointKey {
     // FIXME: Rewrite doc comment when AppEndpointValue members are known
     /// Returns a key for fetching all cached endpoints for a given organization and application.
-    pub fn new(org: OrganizationId, app: ApplicationId) -> AppEndpointKey {
+    pub fn new(org: &OrganizationId, app: &ApplicationId) -> AppEndpointKey {
         AppEndpointKey(format!("{}_APP_v3_{}_{}", Self::PREFIX_CACHE, org, app))
     }
 }
diff --git a/server/svix-server/src/core/otel_spans.rs b/server/svix-server/src/core/otel_spans.rs
index 5b1ff9061..e25a7f528 100644
--- a/server/svix-server/src/core/otel_spans.rs
+++ b/server/svix-server/src/core/otel_spans.rs
@@ -88,7 +88,7 @@ impl<B> MakeSpan<B> for AxumOtelSpanCreator {
             Version::HTTP_11 => "1.1".into(),
             Version::HTTP_2 => "2.0".into(),
             Version::HTTP_3 => "3.0".into(),
-            other => format!("{:?}", other).into(),
+            other => format!("{other:?}").into(),
         };
 
         let method: Cow<'static, str> = match request.method() {
diff --git a/server/svix-server/src/core/permissions.rs b/server/svix-server/src/core/permissions.rs
index 3fef0b9c6..fbaec215e 100644
--- a/server/svix-server/src/core/permissions.rs
+++ b/server/svix-server/src/core/permissions.rs
@@ -1,34 +1,46 @@
 use axum::{
     async_trait,
-    extract::{FromRequest, Path, RequestParts},
+    extract::{FromRequestParts, Path},
+    http::request::Parts,
     Extension,
 };
 use sea_orm::DatabaseConnection;
 
 use crate::{
     ctx,
-    db::models::application,
+    db::models::{application, applicationmetadata},
     error::{Error, HttpError, Result},
 };
 
 use super::{
-    security::{permissions_from_bearer, AccessLevel},
-    types::{ApplicationIdOrUid, OrganizationId},
+    security::{permissions_from_bearer, AccessLevel, Permissions},
+    types::{ApplicationId, ApplicationIdOrUid, OrganizationId},
 };
 
 pub struct Organization {
     pub org_id: OrganizationId,
 }
 
+impl Permissions {
+    fn check_app_is_permitted(&self, app_id: &ApplicationId) -> Result<()> {
+        if let Some(ref permitted_app_id) = self.app_id() {
+            if permitted_app_id != app_id {
+                return Err(HttpError::not_found(None, None).into());
+            }
+        }
+        Ok(())
+    }
+}
+
 #[async_trait]
-impl<B> FromRequest<B> for Organization
+impl<S> FromRequestParts<S> for Organization
 where
-    B: Send,
+    S: Send + Sync,
 {
     type Rejection = Error;
 
-    async fn from_request(req: &mut RequestParts<B>) -> Result<Self> {
-        let permissions = permissions_from_bearer(req).await?;
+    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> {
+        let permissions = permissions_from_bearer(parts, state).await?;
 
         let org_id = match permissions.access_level {
             AccessLevel::Organization(org_id) => org_id,
@@ -44,18 +56,19 @@ pub struct Application {
 }
 
 #[async_trait]
-impl<B> FromRequest<B> for Application
+impl<S> FromRequestParts<S> for Application
 where
-    B: Send,
+    S: Send + Sync,
 {
     type Rejection = Error;
 
-    async fn from_request(req: &mut RequestParts<B>) -> Result<Self> {
-        let permissions = permissions_from_bearer(req).await?;
+    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> {
+        let permissions = permissions_from_bearer(parts, state).await?;
 
         let Path(ApplicationPathParams { app_id }) =
-            ctx!(Path::<ApplicationPathParams>::from_request(req).await)?;
-        let Extension(ref db) = ctx!(Extension::<DatabaseConnection>::from_request(req).await)?;
+            ctx!(Path::<ApplicationPathParams>::from_request_parts(parts, state).await)?;
+        let Extension(ref db) =
+            ctx!(Extension::<DatabaseConnection>::from_request_parts(parts, state).await)?;
         let app = ctx!(
             application::Entity::secure_find_by_id_or_uid(permissions.org_id(), app_id.to_owned(),)
                 .one(db)
@@ -63,11 +76,7 @@ where
         )?
         .ok_or_else(|| HttpError::not_found(None, None))?;
 
-        if let Some(permitted_app_id) = permissions.app_id() {
-            if permitted_app_id != app.id {
-                return Err(HttpError::not_found(None, None).into());
-            }
-        }
+        permissions.check_app_is_permitted(&app.id)?;
 
         Ok(Self { app })
     }
@@ -79,18 +88,19 @@ pub struct OrganizationWithApplication {
 }
 
 #[async_trait]
-impl<B> FromRequest<B> for OrganizationWithApplication
+impl<S> FromRequestParts<S> for OrganizationWithApplication
 where
-    B: Send,
+    S: Send + Sync,
 {
     type Rejection = Error;
 
-    async fn from_request(req: &mut RequestParts<B>) -> Result<Self> {
-        let Organization { org_id } = ctx!(Organization::from_request(req).await)?;
+    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> {
+        let Organization { org_id } = ctx!(Organization::from_request_parts(parts, state).await)?;
 
         let Path(ApplicationPathParams { app_id }) =
-            ctx!(Path::<ApplicationPathParams>::from_request(req).await)?;
-        let Extension(ref db) = ctx!(Extension::<DatabaseConnection>::from_request(req).await)?;
+            ctx!(Path::<ApplicationPathParams>::from_request_parts(parts, state).await)?;
+        let Extension(ref db) =
+            ctx!(Extension::<DatabaseConnection>::from_request_parts(parts, state).await)?;
         let app = ctx!(
             application::Entity::secure_find_by_id_or_uid(org_id, app_id.to_owned(),)
                 .one(db)
@@ -101,6 +111,37 @@ where
     }
 }
 
+pub struct ApplicationWithMetadata {
+    pub app: application::Model,
+    pub metadata: applicationmetadata::Model,
+}
+
+#[async_trait]
+impl<S> FromRequestParts<S> for ApplicationWithMetadata
+where
+    S: Send + Sync,
+{
+    type Rejection = Error;
+
+    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> {
+        let permissions = permissions_from_bearer(parts, state).await?;
+
+        let Path(ApplicationPathParams { app_id }) =
+            ctx!(Path::<ApplicationPathParams>::from_request_parts(parts, state).await)?;
+        let Extension(ref db) =
+            ctx!(Extension::<DatabaseConnection>::from_request_parts(parts, state).await)?;
+        let (app, metadata) = ctx!(
+            application::Model::fetch_with_metadata(db, permissions.org_id(), app_id.to_owned())
+                .await
+        )?
+        .ok_or_else(|| HttpError::not_found(None, None))?;
+
+        permissions.check_app_is_permitted(&app.id)?;
+
+        Ok(Self { app, metadata })
+    }
+}
+
 #[derive(serde::Deserialize)]
 struct ApplicationPathParams {
     app_id: ApplicationIdOrUid,
diff --git a/server/svix-server/src/core/security.rs b/server/svix-server/src/core/security.rs
index 57360e707..10686a77b 100644
--- a/server/svix-server/src/core/security.rs
+++ b/server/svix-server/src/core/security.rs
@@ -4,10 +4,11 @@
 use std::fmt::Debug;
 
 use axum::{
-    extract::{Extension, FromRequest, RequestParts, TypedHeader},
+    extract::{Extension, FromRequestParts, TypedHeader},
     headers::{authorization::Bearer, Authorization},
 };
 
+use http::request::Parts;
 use jwt_simple::prelude::*;
 
 use validator::Validate;
@@ -61,20 +62,23 @@ pub struct CustomClaim {
     pub organization: Option<String>,
 }
 
-pub async fn permissions_from_bearer<B: Send>(req: &mut RequestParts<B>) -> Result<Permissions> {
-    let Extension(ref cfg) = ctx!(Extension::<Configuration>::from_request(req).await)?;
+pub async fn permissions_from_bearer<S: Send + Sync>(
+    parts: &mut Parts,
+    state: &S,
+) -> Result<Permissions> {
+    let Extension(ref cfg) =
+        ctx!(Extension::<Configuration>::from_request_parts(parts, state).await)?;
 
     let TypedHeader(Authorization(bearer)) =
-        ctx!(TypedHeader::<Authorization<Bearer>>::from_request(req).await)?;
+        ctx!(TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state).await)?;
 
-    let claims = parse_bearer(cfg, &bearer)
+    let claims = parse_bearer(&cfg.jwt_secret, &bearer)
         .ok_or_else(|| HttpError::unauthorized(None, Some("Invalid token".to_string())))?;
     permissions_from_jwt(claims)
 }
 
-pub fn parse_bearer(cfg: &Configuration, bearer: &Bearer) -> Option<JWTClaims<CustomClaim>> {
-    cfg.jwt_secret
-        .key
+pub fn parse_bearer(key: &Keys, bearer: &Bearer) -> Option<JWTClaims<CustomClaim>> {
+    key.key
         .verify_token::<CustomClaim>(bearer.token(), None)
         .ok()
 }
@@ -83,7 +87,7 @@ pub fn permissions_from_jwt(claims: JWTClaims<CustomClaim>) -> Result<Permission
     let bad_token = |field: &str, id_type: &str| {
         HttpError::bad_request(
             Some("bad token".to_string()),
-            Some(format!("`{}` is not a valid {} id", field, id_type)),
+            Some(format!("`{field}` is not a valid {id_type} id")),
         )
     };
 
diff --git a/server/svix-server/src/core/types/metadata.rs b/server/svix-server/src/core/types/metadata.rs
new file mode 100644
index 000000000..5bab624bc
--- /dev/null
+++ b/server/svix-server/src/core/types/metadata.rs
@@ -0,0 +1,39 @@
+use std::collections::HashMap;
+
+use crate::json_wrapper;
+use serde::{Deserialize, Serialize};
+
+pub const MAX_METADATA_SIZE: usize = 4096;
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Default)]
+pub struct Metadata(HashMap<String, String>);
+
+json_wrapper!(Metadata);
+
+impl Metadata {
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+
+impl<'de> Deserialize<'de> for Metadata {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let inner: Option<HashMap<String, String>> = Deserialize::deserialize(deserializer)?;
+        let metadata = inner.unwrap_or_default(); // coerce `null` to `{}`
+
+        let size = serde_json::to_string(&metadata)
+            .map(|blob| blob.len())
+            .map_err(|_| serde::de::Error::custom("metadata is not valid json"))?;
+
+        if size > MAX_METADATA_SIZE {
+            return Err(serde::de::Error::custom(format!(
+                "metadata must be less than or equal to {MAX_METADATA_SIZE} bytes"
+            )));
+        }
+
+        Ok(Self(metadata))
+    }
+}
diff --git a/server/svix-server/src/core/types.rs b/server/svix-server/src/core/types/mod.rs
similarity index 90%
rename from server/svix-server/src/core/types.rs
rename to server/svix-server/src/core/types/mod.rs
index f074bd405..cb4534fc2 100644
--- a/server/svix-server/src/core/types.rs
+++ b/server/svix-server/src/core/types/mod.rs
@@ -14,11 +14,7 @@ use p256::ecdsa::{
 use rand::Rng;
 
 use regex::Regex;
-use sea_orm::{
-    entity::prelude::*,
-    sea_query::{ColumnType, Nullable, ValueType, ValueTypeErr},
-    TryFromU64, TryGetError, TryGetable,
-};
+
 use serde::{Deserialize, Serialize};
 use std::ops::Deref;
 use svix_ksuid::*;
@@ -26,6 +22,8 @@ use validator::{Validate, ValidationErrors};
 
 use crate::{cfg::DefaultSignatureType, err_generic, v1::utils::validation_error};
 
+pub mod metadata;
+
 use super::cryptography::{AsymmetricKey, AsymmetricKeyP256, Encryption};
 
 const ALL_ERROR: &str = "__all__";
@@ -53,33 +51,41 @@ macro_rules! enum_wrapper {
             }
         }
 
-        impl From<$name_id> for sea_orm::Value {
+        impl From<$name_id> for sea_orm::entity::prelude::Value {
             fn from(v: $name_id) -> Self {
                 Self::SmallInt(Some(v.into()))
             }
         }
 
-        impl TryGetable for $name_id {
-            fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
+        impl sea_orm::TryGetable for $name_id {
+            fn try_get(
+                res: &sea_orm::QueryResult,
+                pre: &str,
+                col: &str,
+            ) -> Result<Self, sea_orm::TryGetError> {
                 match i16::try_get(res, pre, col) {
-                    // We are using null as a placeholder error for invalid values
-                    Ok(v) => v.try_into().map_err(|_| TryGetError::Null),
+                    // We are using null as a placeholder error for invalid sea_orm::Values
+                    Ok(v) => v
+                        .try_into()
+                        .map_err(|_| sea_orm::TryGetError::Null("invalid sea_orm_value".into())),
                     Err(e) => Err(e),
                 }
             }
         }
 
-        impl Nullable for $name_id {
-            fn null() -> Value {
-                Value::SmallInt(None)
+        impl sea_orm::sea_query::Nullable for $name_id {
+            fn null() -> sea_orm::Value {
+                sea_orm::Value::SmallInt(None)
             }
         }
 
-        impl ValueType for $name_id {
-            fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
+        impl sea_orm::sea_query::ValueType for $name_id {
+            fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
                 match v {
-                    Value::SmallInt(Some(x)) => x.try_into().map_err(|_| ValueTypeErr),
-                    _ => Err(ValueTypeErr),
+                    sea_orm::Value::SmallInt(Some(x)) => {
+                        x.try_into().map_err(|_| sea_orm::sea_query::ValueTypeErr)
+                    }
+                    _ => Err(sea_orm::sea_query::ValueTypeErr),
                 }
             }
 
@@ -87,9 +93,13 @@ macro_rules! enum_wrapper {
                 stringify!($name_id).to_owned()
             }
 
-            fn column_type() -> ColumnType {
+            fn column_type() -> sea_orm::sea_query::ColumnType {
                 i16::column_type()
             }
+
+            fn array_type() -> sea_orm::sea_query::ArrayType {
+                i16::array_type()
+            }
         }
     };
 }
@@ -97,7 +107,7 @@ macro_rules! enum_wrapper {
 #[macro_export]
 macro_rules! json_wrapper {
     ($name_id:ty) => {
-        impl From<$name_id> for sea_orm::Value {
+        impl From<$name_id> for sea_orm::entity::prelude::Value {
             fn from(v: $name_id) -> Self {
                 let v = serde_json::to_value(v).expect("Error serializing JSON");
                 Self::Json(Some(Box::new(v)))
@@ -106,11 +116,11 @@ macro_rules! json_wrapper {
 
         impl sea_orm::TryGetable for $name_id {
             fn try_get(
-                res: &QueryResult,
+                res: &sea_orm::QueryResult,
                 pre: &str,
                 col: &str,
             ) -> Result<Self, sea_orm::TryGetError> {
-                match Json::try_get(res, pre, col) {
+                match sea_orm::prelude::Json::try_get(res, pre, col) {
                     Ok(v) => Ok(serde_json::from_value(v).expect("Error deserializing JSON")),
                     Err(e) => Err(e),
                 }
@@ -118,15 +128,15 @@ macro_rules! json_wrapper {
         }
 
         impl sea_orm::sea_query::Nullable for $name_id {
-            fn null() -> Value {
-                Value::Json(None)
+            fn null() -> sea_orm::Value {
+                sea_orm::Value::Json(None)
             }
         }
 
         impl sea_orm::sea_query::ValueType for $name_id {
-            fn try_from(v: Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
+            fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
                 match v {
-                    Value::Json(Some(x)) => {
+                    sea_orm::Value::Json(Some(x)) => {
                         Ok(serde_json::from_value(*x).expect("Error deserializing JSON"))
                     }
                     _ => Err(sea_orm::sea_query::ValueTypeErr),
@@ -140,6 +150,10 @@ macro_rules! json_wrapper {
             fn column_type() -> sea_orm::sea_query::ColumnType {
                 sea_orm::sea_query::ColumnType::JsonBinary
             }
+
+            fn array_type() -> sea_orm::sea_query::ArrayType {
+                sea_orm::sea_query::ArrayType::Json
+            }
         }
     };
 }
@@ -252,8 +266,12 @@ macro_rules! string_wrapper {
             }
         }
 
-        impl TryGetable for $name_id {
-            fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
+        impl sea_orm::TryGetable for $name_id {
+            fn try_get(
+                res: &sea_orm::QueryResult,
+                pre: &str,
+                col: &str,
+            ) -> Result<Self, sea_orm::TryGetError> {
                 match String::try_get(res, pre, col) {
                     Ok(v) => Ok($name_id(v)),
                     Err(e) => Err(e),
@@ -261,17 +279,17 @@ macro_rules! string_wrapper {
             }
         }
 
-        impl Nullable for $name_id {
-            fn null() -> Value {
-                Value::String(None)
+        impl sea_orm::sea_query::Nullable for $name_id {
+            fn null() -> sea_orm::Value {
+                sea_orm::Value::String(None)
             }
         }
 
-        impl ValueType for $name_id {
-            fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
+        impl sea_orm::sea_query::ValueType for $name_id {
+            fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
                 match v {
-                    Value::String(Some(x)) => Ok($name_id(*x)),
-                    _ => Err(ValueTypeErr),
+                    sea_orm::Value::String(Some(x)) => Ok($name_id(*x)),
+                    _ => Err(sea_orm::sea_query::ValueTypeErr),
                 }
             }
 
@@ -279,9 +297,13 @@ macro_rules! string_wrapper {
                 stringify!($name_id).to_owned()
             }
 
-            fn column_type() -> ColumnType {
+            fn column_type() -> sea_orm::sea_query::ColumnType {
                 String::column_type()
             }
+
+            fn array_type() -> sea_orm::sea_query::ArrayType {
+                String::array_type()
+            }
         }
 
         impl std::fmt::Display for $name_id {
@@ -311,11 +333,10 @@ macro_rules! create_id_type {
             }
         }
 
-        impl TryFromU64 for $name_id {
-            fn try_from_u64(_: u64) -> Result<Self, DbErr> {
-                Err(DbErr::Exec(format!(
-                    "{} cannot be converted from u64",
-                    stringify!($type)
+        impl sea_orm::TryFromU64 for $name_id {
+            fn try_from_u64(_: u64) -> Result<Self, sea_orm::DbErr> {
+                Err(sea_orm::DbErr::Exec(sea_orm::error::RuntimeErr::Internal(
+                    format!("{} cannot be converted from u64", stringify!($type)),
                 )))
             }
         }
@@ -731,27 +752,33 @@ impl From<EndpointSecretInternal> for sea_orm::Value {
     }
 }
 
-impl TryGetable for EndpointSecretInternal {
-    fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
+impl sea_orm::TryGetable for EndpointSecretInternal {
+    fn try_get(
+        res: &sea_orm::QueryResult,
+        pre: &str,
+        col: &str,
+    ) -> Result<Self, sea_orm::TryGetError> {
         match Vec::<u8>::try_get(res, pre, col) {
             Ok(v) => EndpointSecretInternal::from_vec(v)
-                .map_err(|x| TryGetError::DbErr(DbErr::Type(x.to_string()))),
+                .map_err(|x| sea_orm::TryGetError::DbErr(sea_orm::DbErr::Type(x.to_string()))),
             Err(e) => Err(e),
         }
     }
 }
 
-impl Nullable for EndpointSecretInternal {
-    fn null() -> Value {
-        Value::Bytes(None)
+impl sea_orm::sea_query::Nullable for EndpointSecretInternal {
+    fn null() -> sea_orm::Value {
+        sea_orm::Value::Bytes(None)
     }
 }
 
-impl ValueType for EndpointSecretInternal {
-    fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
+impl sea_orm::sea_query::ValueType for EndpointSecretInternal {
+    fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
         match v {
-            Value::Bytes(Some(x)) => EndpointSecretInternal::from_vec(*x).map_err(|_| ValueTypeErr),
-            _ => Err(ValueTypeErr),
+            sea_orm::Value::Bytes(Some(x)) => {
+                EndpointSecretInternal::from_vec(*x).map_err(|_| sea_orm::sea_query::ValueTypeErr)
+            }
+            _ => Err(sea_orm::sea_query::ValueTypeErr),
         }
     }
 
@@ -759,8 +786,12 @@ impl ValueType for EndpointSecretInternal {
         stringify!(EndpointSecretInternal).to_owned()
     }
 
-    fn column_type() -> ColumnType {
-        ColumnType::Binary(None)
+    fn column_type() -> sea_orm::sea_query::ColumnType {
+        sea_orm::sea_query::ColumnType::Binary(sea_orm::sea_query::BlobSize::Blob(None))
+    }
+
+    fn array_type() -> sea_orm::sea_query::ArrayType {
+        sea_orm::sea_query::ArrayType::Bytes
     }
 }
 
@@ -1278,7 +1309,7 @@ mod tests {
 
         // Valid long secret
         let long_sec = "TUdfVE5UMnZlci1TeWxOYXQtX1ZlTW1kLTRtMFdhYmEwanIxdHJvenRCbmlTQ2hFdzBnbHhFbWdFaTJLdzQwSA==";
-        let js = serde_json::json!({ "key": format!("whsec_{}", long_sec) });
+        let js = serde_json::json!({ "key": format!("whsec_{long_sec}") });
         let ep = serde_json::from_value::<EndpointSecretTestStruct>(js).unwrap();
         if let EndpointSecret::Symmetric(key) = ep.key {
             assert_eq!(base64::decode(long_sec).unwrap(), key);
@@ -1288,7 +1319,7 @@ mod tests {
 
         // Asymmetric key
         let asym_sec = "6Xb/dCcHpPea21PS1N9VY/NZW723CEc77N4rJCubMbfVKIDij2HKpMKkioLlX0dRqSKJp4AJ6p9lMicMFs6Kvg==";
-        let js = serde_json::json!({ "key": format!("whsk_{}", asym_sec) });
+        let js = serde_json::json!({ "key": format!("whsk_{asym_sec}") });
         let ep = serde_json::from_value::<EndpointSecretTestStruct>(js).unwrap();
         if let EndpointSecret::Asymmetric(key) = ep.key {
             assert_eq!(base64::decode(asym_sec).unwrap(), key.0.sk.as_slice());
diff --git a/server/svix-server/src/db/mod.rs b/server/svix-server/src/db/mod.rs
index 1b4d42d84..dcb8126d5 100644
--- a/server/svix-server/src/db/mod.rs
+++ b/server/svix-server/src/db/mod.rs
@@ -14,25 +14,25 @@ use models::{application, endpoint, eventtype, message, messageattempt, messaged
 
 static MIGRATIONS: sqlx::migrate::Migrator = sqlx::migrate!();
 
-async fn connect(cfg: &Configuration) -> sqlx::Pool<sqlx::Postgres> {
+async fn connect(dsn: &str, max_pool_size: u16) -> sqlx::Pool<sqlx::Postgres> {
     tracing::debug!("DB: Initializing pool");
-    if DbBackend::Postgres.is_prefix_of(&cfg.db_dsn) {
+    if DbBackend::Postgres.is_prefix_of(dsn) {
         PgPoolOptions::new()
-            .max_connections(cfg.db_pool_max_size.into())
-            .connect(&cfg.db_dsn)
+            .max_connections(max_pool_size.into())
+            .connect(dsn)
             .await
             .expect("Error connectiong to Postgres")
     } else {
-        panic!("db_dsn format not recognized. {}", &cfg.db_dsn)
+        panic!("db_dsn format not recognized. {dsn}")
     }
 }
 
 pub async fn init_db(cfg: &Configuration) -> DatabaseConnection {
-    SqlxPostgresConnector::from_sqlx_postgres_pool(connect(cfg).await)
+    SqlxPostgresConnector::from_sqlx_postgres_pool(connect(&cfg.db_dsn, cfg.db_pool_max_size).await)
 }
 
 pub async fn run_migrations(cfg: &Configuration) {
-    let db = connect(cfg).await;
+    let db = connect(&cfg.db_dsn, cfg.db_pool_max_size).await;
     MIGRATIONS.run(&db).await.unwrap();
 }
 
@@ -44,12 +44,7 @@ pub async fn wipe_org(cfg: &Configuration, org_id: OrganizationId) {
     let applications: Vec<application::Model> = application::Entity::secure_find(org_id.clone())
         .all(&db)
         .await
-        .unwrap_or_else(|_| {
-            panic!(
-                "Error fetching applications associated with org ID {}",
-                org_id
-            )
-        });
+        .unwrap_or_else(|_| panic!("Error fetching applications associated with org ID {org_id}"));
 
     for application in applications {
         let endpoints: Vec<endpoint::Model> = endpoint::Entity::secure_find(application.id.clone())
@@ -116,21 +111,24 @@ pub async fn wipe_org(cfg: &Configuration, org_id: OrganizationId) {
         .filter(application::Column::OrgId.eq(org_id.clone()))
         .exec(&db)
         .await
-        .unwrap_or_else(|_| {
-            panic!(
-                "Error deleting applications associated with org ID {}",
-                org_id
-            )
-        });
+        .unwrap_or_else(|_| panic!("Error deleting applications associated with org ID {org_id}"));
 
     let _: DeleteResult = eventtype::Entity::delete_many()
         .filter(eventtype::Column::OrgId.eq(org_id.clone()))
         .exec(&db)
         .await
-        .unwrap_or_else(|_| {
-            panic!(
-                "Error deleting event types associated with org ID {}",
-                org_id
-            )
-        });
+        .unwrap_or_else(|_| panic!("Error deleting event types associated with org ID {org_id}"));
+}
+
+#[macro_export]
+/// Runs an async closure inside of a DB Transaction. The closure should return an [`error::Result<T>`]. If the closure returns an error for any reason, the transaction is rolled back.
+macro_rules! transaction {
+    ($db:expr, $do:expr) => {
+        $crate::ctx!(
+            sea_orm::TransactionTrait::transaction::<_, _, $crate::error::Error>($db, |txn| {
+                std::boxed::Box::pin({ $do(txn) })
+            })
+            .await
+        )
+    };
 }
diff --git a/server/svix-server/src/db/models/application.rs b/server/svix-server/src/db/models/application.rs
index 863e0f118..443ce5209 100644
--- a/server/svix-server/src/db/models/application.rs
+++ b/server/svix-server/src/db/models/application.rs
@@ -4,9 +4,13 @@
 use crate::core::types::{
     ApplicationId, ApplicationIdOrUid, ApplicationUid, BaseId, OrganizationId,
 };
+use crate::{ctx, error};
 use chrono::Utc;
 use sea_orm::ActiveValue::Set;
 use sea_orm::{entity::prelude::*, Condition};
+use sea_orm::{ConnectionTrait, QueryOrder, QuerySelect};
+
+use super::applicationmetadata;
 
 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
 #[sea_orm(table_name = "application")]
@@ -28,6 +32,8 @@ pub enum Relation {
     Endpoint,
     #[sea_orm(has_many = "super::message::Entity")]
     Message,
+    #[sea_orm(has_one = "super::applicationmetadata::Entity")]
+    Metadata,
 }
 
 impl Related<super::endpoint::Entity> for Entity {
@@ -42,22 +48,91 @@ impl Related<super::message::Entity> for Entity {
     }
 }
 
+impl Related<super::applicationmetadata::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Metadata.def()
+    }
+}
+
+impl Model {
+    pub async fn fetch_or_create_metadata(
+        &self,
+        db: &impl ConnectionTrait,
+    ) -> error::Result<applicationmetadata::ActiveModel> {
+        let query = applicationmetadata::Entity::secure_find(self.id.clone());
+        let metadata = ctx!(query.one(db).await)?
+            .map(applicationmetadata::ActiveModel::from)
+            .unwrap_or_else(|| applicationmetadata::ActiveModel::new(self.id.clone(), None));
+
+        Ok(metadata)
+    }
+
+    pub async fn fetch_many_with_metadata(
+        db: &DatabaseConnection,
+        org_id: OrganizationId,
+        limit: u64,
+        after_id: impl Into<Option<ApplicationId>>,
+    ) -> error::Result<impl Iterator<Item = (Self, applicationmetadata::Model)>> {
+        let mut query = Entity::secure_find(org_id)
+            .order_by_asc(Column::Id)
+            .limit(limit);
+
+        if let Some(id) = after_id.into() {
+            query = query.filter(Column::Id.gt(id))
+        }
+
+        let results = ctx!(
+            query
+                .find_also_related(applicationmetadata::Entity)
+                .all(db)
+                .await
+        )?;
+
+        Ok(results.into_iter().map(|(app, metadata)| {
+            let metadata =
+                metadata.unwrap_or_else(|| applicationmetadata::Model::new(app.id.clone()));
+            (app, metadata)
+        }))
+    }
+
+    pub async fn fetch_with_metadata(
+        db: &DatabaseConnection,
+        org_id: OrganizationId,
+        id_or_uid: ApplicationIdOrUid,
+    ) -> error::Result<Option<(Self, applicationmetadata::Model)>> {
+        let result = ctx!(
+            Entity::secure_find_by_id_or_uid(org_id, id_or_uid)
+                .find_also_related(applicationmetadata::Entity)
+                .one(db)
+                .await
+        )?;
+        Ok(result.map(|(app, metadata)| {
+            let metadata =
+                metadata.unwrap_or_else(|| applicationmetadata::Model::new(app.id.clone()));
+            (app, metadata)
+        }))
+    }
+}
+
 impl ActiveModelBehavior for ActiveModel {
-    fn new() -> Self {
+    fn before_save(mut self, _insert: bool) -> Result<Self, DbErr> {
+        self.updated_at = Set(Utc::now().into());
+        Ok(self)
+    }
+}
+
+impl ActiveModel {
+    pub fn new(org_id: OrganizationId) -> Self {
         let timestamp = Utc::now();
         Self {
             id: Set(ApplicationId::new(timestamp.into(), None)),
+            org_id: Set(org_id),
             created_at: Set(timestamp.into()),
             updated_at: Set(timestamp.into()),
             deleted: Set(false),
             ..ActiveModelTrait::default()
         }
     }
-
-    fn before_save(mut self, _insert: bool) -> Result<Self, DbErr> {
-        self.updated_at = Set(Utc::now().into());
-        Ok(self)
-    }
 }
 
 impl Entity {
diff --git a/server/svix-server/src/db/models/applicationmetadata.rs b/server/svix-server/src/db/models/applicationmetadata.rs
new file mode 100644
index 000000000..b5d9f838c
--- /dev/null
+++ b/server/svix-server/src/db/models/applicationmetadata.rs
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: © 2022 Svix Authors
+// SPDX-License-Identifier: MIT
+use crate::core::types::ApplicationId;
+
+use crate::core::types::metadata::Metadata;
+use crate::{ctx, error};
+use chrono::Utc;
+use sea_orm::entity::prelude::*;
+use sea_orm::sea_query::OnConflict;
+use sea_orm::ActiveValue::Set;
+use sea_orm::{ConnectionTrait, TryIntoModel};
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "applicationmetadata")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: ApplicationId,
+    pub created_at: DateTimeWithTimeZone,
+    pub updated_at: DateTimeWithTimeZone,
+    pub data: Metadata,
+}
+
+impl Model {
+    pub fn metadata(self) -> Metadata {
+        self.data
+    }
+
+    pub fn new(app_id: ApplicationId) -> Self {
+        ActiveModel::new(app_id, None)
+            .try_into_model()
+            .expect("ActiveModel::create(...) should have set all fields")
+    }
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::application::Entity",
+        from = "Column::Id",
+        to = "super::application::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Restrict"
+    )]
+    Application,
+}
+
+impl Related<super::application::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Application.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {
+    fn before_save(mut self, _insert: bool) -> Result<Self, DbErr> {
+        self.updated_at = Set(Utc::now().into());
+        Ok(self)
+    }
+}
+
+impl ActiveModel {
+    pub fn new(app_id: ApplicationId, metadata: impl Into<Option<Metadata>>) -> Self {
+        let id = Set(app_id);
+        let data = Set(metadata.into().unwrap_or_default());
+        let timestamp = Utc::now();
+        Self {
+            id,
+            data,
+            created_at: Set(timestamp.into()),
+            updated_at: Set(timestamp.into()),
+        }
+    }
+
+    /// Upserts the record if it's new or updated, AND data is nonempty. Otherwise the record is
+    /// ignored or destroyed as appropriate.
+    pub async fn upsert_or_delete(self, db: &impl ConnectionTrait) -> error::Result<Model> {
+        let data = self.data.clone().take().unwrap_or_default();
+
+        if data.is_empty() {
+            let model = ctx!(self.clone().try_into_model())?;
+            ctx!(self.delete(db).await)?;
+            return Ok(model);
+        }
+
+        ctx!(Entity::upsert(self).exec_with_returning(db).await)
+    }
+}
+
+impl Entity {
+    pub fn secure_find(app_id: ApplicationId) -> sea_orm::Select<Entity> {
+        Self::find().filter(Column::Id.eq(app_id))
+    }
+
+    pub fn upsert(am: ActiveModel) -> sea_orm::Insert<ActiveModel> {
+        Self::insert(am).on_conflict(
+            OnConflict::column(Column::Id)
+                .update_columns([Column::Data, Column::UpdatedAt])
+                .to_owned(),
+        )
+    }
+}
diff --git a/server/svix-server/src/db/models/endpoint.rs b/server/svix-server/src/db/models/endpoint.rs
index 2697d1b7d..e1e8cfc93 100644
--- a/server/svix-server/src/db/models/endpoint.rs
+++ b/server/svix-server/src/db/models/endpoint.rs
@@ -44,6 +44,8 @@ pub enum Relation {
     Application,
     #[sea_orm(has_many = "super::messagedestination::Entity")]
     Messagedestination,
+    #[sea_orm(has_one = "super::endpointmetadata::Entity")]
+    Metadata,
 }
 
 impl Related<super::application::Entity> for Entity {
@@ -58,6 +60,12 @@ impl Related<super::messagedestination::Entity> for Entity {
     }
 }
 
+impl Related<super::endpointmetadata::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Metadata.def()
+    }
+}
+
 impl ActiveModelBehavior for ActiveModel {
     fn new() -> Self {
         let timestamp = Utc::now();
diff --git a/server/svix-server/src/db/models/endpointmetadata.rs b/server/svix-server/src/db/models/endpointmetadata.rs
new file mode 100644
index 000000000..efcfcf72e
--- /dev/null
+++ b/server/svix-server/src/db/models/endpointmetadata.rs
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: © 2022 Svix Authors
+// SPDX-License-Identifier: MIT
+use crate::core::types::EndpointId;
+
+use crate::core::types::metadata::Metadata;
+
+use chrono::Utc;
+use sea_orm::entity::prelude::*;
+use sea_orm::ActiveValue::Set;
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "applicationmetadata")]
+pub struct Model {
+    #[sea_orm(primary_key, auto_increment = false)]
+    pub id: EndpointId,
+    pub created_at: DateTimeWithTimeZone,
+    pub updated_at: DateTimeWithTimeZone,
+    pub data: Metadata,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+    #[sea_orm(
+        belongs_to = "super::endpoint::Entity",
+        from = "Column::Id",
+        to = "super::endpoint::Column::Id",
+        on_update = "NoAction",
+        on_delete = "Restrict"
+    )]
+    Endpoint,
+}
+
+impl Related<super::endpoint::Entity> for Entity {
+    fn to() -> RelationDef {
+        Relation::Endpoint.def()
+    }
+}
+
+impl ActiveModelBehavior for ActiveModel {
+    fn before_save(mut self, _insert: bool) -> Result<Self, DbErr> {
+        self.updated_at = Set(Utc::now().into());
+        Ok(self)
+    }
+}
diff --git a/server/svix-server/src/db/models/mod.rs b/server/svix-server/src/db/models/mod.rs
index 2698afcef..1d5bd1f28 100644
--- a/server/svix-server/src/db/models/mod.rs
+++ b/server/svix-server/src/db/models/mod.rs
@@ -2,7 +2,9 @@
 // SPDX-License-Identifier: MIT
 
 pub mod application;
+pub mod applicationmetadata;
 pub mod endpoint;
+pub mod endpointmetadata;
 pub mod eventtype;
 pub mod message;
 pub mod messageattempt;
diff --git a/server/svix-server/src/error.rs b/server/svix-server/src/error.rs
index 663d73eca..d51079cf4 100644
--- a/server/svix-server/src/error.rs
+++ b/server/svix-server/src/error.rs
@@ -15,8 +15,11 @@ use axum::Json;
 use axum::TypedHeader;
 use hyper::StatusCode;
 use sea_orm::DbErr;
+use sea_orm::RuntimeErr;
+use sea_orm::TransactionError;
 use serde::Serialize;
 use serde_json::json;
+use sqlx::Error as SqlxError;
 
 /// A short-hand version of a [std::result::Result] that always returns an Svix [Error].
 pub type Result<T> = std::result::Result<T, Error>;
@@ -171,11 +174,23 @@ impl<T> Traceable<T> for Result<T> {
 impl<T> Traceable<T> for std::result::Result<T, DbErr> {
     fn trace(self, location: &'static str) -> Result<T> {
         self.map_err(|err| {
-            let typ = if let DbErr::Query(ref err_str) = err {
-                if err_str.contains("duplicate key value violates unique constraint") {
-                    ErrorType::Http(HttpError::conflict(None, None))
-                } else {
-                    ErrorType::Database(err.to_string())
+            let typ = if let DbErr::Query(runtime_error) = err {
+                match runtime_error {
+                    RuntimeErr::SqlxError(sqlx_err) => {
+                        if matches!(sqlx_err, SqlxError::RowNotFound) {
+                            HttpError::not_found(None, None).into()
+                        } else if sqlx_err
+                            .as_database_error()
+                            .and_then(|e| e.code())
+                            .filter(|code| code == "23505") // "duplicate key value violates unique constraint"
+                            .is_some()
+                        {
+                            HttpError::conflict(None, None).into()
+                        } else {
+                            ErrorType::Database(sqlx_err.to_string())
+                        }
+                    }
+                    RuntimeErr::Internal(err) => ErrorType::Database(err),
                 }
             } else {
                 ErrorType::Database(err.to_string())
@@ -227,6 +242,15 @@ impl<T> Traceable<T> for std::result::Result<T, crate::core::cache::Error> {
     }
 }
 
+impl<T> Traceable<T> for std::result::Result<T, TransactionError<Error>> {
+    fn trace(self, location: &'static str) -> Result<T> {
+        self.map_err(|e| match e {
+            TransactionError::Connection(db_err) => Error::database(db_err, location),
+            TransactionError::Transaction(crate_err) => crate_err, // preserve the trace that comes from within the transaction
+        })
+    }
+}
+
 #[derive(Debug, Clone)]
 pub enum ErrorType {
     /// A generic error
@@ -256,6 +280,12 @@ impl fmt::Display for ErrorType {
     }
 }
 
+impl From<HttpError> for ErrorType {
+    fn from(e: HttpError) -> Self {
+        Self::Http(e)
+    }
+}
+
 #[derive(Debug, Clone, Serialize)]
 #[serde(untagged)]
 pub enum HttpErrorBody {
@@ -380,7 +410,7 @@ impl fmt::Display for HttpError {
                     "status={} detail={}",
                     self.status,
                     serde_json::to_string(&detail)
-                        .unwrap_or_else(|e| format!("\"unserializable error for {}\"", e))
+                        .unwrap_or_else(|e| format!("\"unserializable error for {e}\""))
                 )
             }
         }
diff --git a/server/svix-server/src/lib.rs b/server/svix-server/src/lib.rs
index e8811f096..54ed64795 100644
--- a/server/svix-server/src/lib.rs
+++ b/server/svix-server/src/lib.rs
@@ -6,7 +6,10 @@
 
 use axum::{extract::Extension, Router};
 
+use cfg::ConfigurationInner;
 use lazy_static::lazy_static;
+use opentelemetry::runtime::Tokio;
+use opentelemetry_otlp::WithExportConfig;
 use std::{
     net::TcpListener,
     sync::atomic::{AtomicBool, Ordering},
@@ -15,6 +18,7 @@ use std::{
 use tower::ServiceBuilder;
 use tower_http::cors::{AllowHeaders, Any, CorsLayer};
 use tower_http::trace::TraceLayer;
+use tracing_subscriber::{prelude::*, util::SubscriberInitExt};
 
 use crate::{
     cfg::{CacheBackend, Configuration},
@@ -39,6 +43,8 @@ pub mod redis;
 pub mod v1;
 pub mod worker;
 
+const CRATE_NAME: &str = env!("CARGO_CRATE_NAME");
+
 lazy_static! {
     pub static ref SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
 }
@@ -106,13 +112,14 @@ pub async fn run_with_prefix(
         cfg.operational_webhook_address.clone(),
     );
 
+    let svc_cache = cache.clone();
     // build our application with a route
     let app = Router::new()
         .nest("/api/v1", v1::router())
         .merge(docs::router())
         .layer(
-            ServiceBuilder::new().layer_fn(|service| IdempotencyService {
-                cache: cache.clone(),
+            ServiceBuilder::new().layer_fn(move |service| IdempotencyService {
+                cache: svc_cache.clone(),
                 service,
             }),
         )
@@ -166,7 +173,15 @@ pub async fn run_with_prefix(
         async {
             if with_worker {
                 tracing::debug!("Worker: Initializing");
-                worker_loop(&cfg, &pool, cache, queue_tx, queue_rx, op_webhook_sender).await
+                worker_loop(
+                    &cfg,
+                    &pool,
+                    cache.clone(),
+                    queue_tx,
+                    queue_rx,
+                    op_webhook_sender,
+                )
+                .await
             } else {
                 tracing::debug!("Worker: off");
                 Ok(())
@@ -188,6 +203,79 @@ pub async fn run_with_prefix(
     expired_message_cleaner_loop.expect("Error initializing expired message cleaner")
 }
 
+pub fn setup_tracing(cfg: &ConfigurationInner) {
+    if std::env::var_os("RUST_LOG").is_none() {
+        let level = cfg.log_level.to_string();
+        let mut var = vec![
+            format!("{CRATE_NAME}={level}"),
+            format!("tower_http={level}"),
+        ];
+
+        if cfg.db_tracing {
+            var.push(format!("sqlx={level}"));
+        }
+
+        std::env::set_var("RUST_LOG", var.join(","));
+    }
+
+    let otel_layer = cfg.opentelemetry_address.as_ref().map(|addr| {
+        // Configure the OpenTelemetry tracing layer
+        opentelemetry::global::set_text_map_propagator(
+            opentelemetry::sdk::propagation::TraceContextPropagator::new(),
+        );
+
+        let exporter = opentelemetry_otlp::new_exporter()
+            .tonic()
+            .with_endpoint(addr);
+
+        let tracer = opentelemetry_otlp::new_pipeline()
+            .tracing()
+            .with_exporter(exporter)
+            .with_trace_config(
+                opentelemetry::sdk::trace::config()
+                    .with_sampler(
+                        cfg.opentelemetry_sample_ratio
+                            .map(opentelemetry::sdk::trace::Sampler::TraceIdRatioBased)
+                            .unwrap_or(opentelemetry::sdk::trace::Sampler::AlwaysOn),
+                    )
+                    .with_resource(opentelemetry::sdk::Resource::new(vec![
+                        opentelemetry::KeyValue::new("service.name", "svix_server"),
+                    ])),
+            )
+            .install_batch(Tokio)
+            .unwrap();
+        tracing_opentelemetry::layer().with_tracer(tracer)
+    });
+
+    // Then initialize logging with an additional layer priting to stdout. This additional layer is
+    // either formatted normally or in JSON format
+    // Fails if the subscriber was already initialized, which we can safely and silently ignore
+    let _ = match cfg.log_format {
+        cfg::LogFormat::Default => {
+            let stdout_layer = tracing_subscriber::fmt::layer();
+            tracing_subscriber::Registry::default()
+                .with(otel_layer)
+                .with(stdout_layer)
+                .with(tracing_subscriber::EnvFilter::from_default_env())
+                .try_init()
+        }
+        cfg::LogFormat::Json => {
+            let fmt = tracing_subscriber::fmt::format().json().flatten_event(true);
+            let json_fields = tracing_subscriber::fmt::format::JsonFields::new();
+
+            let stdout_layer = tracing_subscriber::fmt::layer()
+                .event_format(fmt)
+                .fmt_fields(json_fields);
+
+            tracing_subscriber::Registry::default()
+                .with(otel_layer)
+                .with(stdout_layer)
+                .with(tracing_subscriber::EnvFilter::from_default_env())
+                .try_init()
+        }
+    };
+}
+
 mod docs {
     use axum::{
         response::{Html, IntoResponse, Redirect},
diff --git a/server/svix-server/src/main.rs b/server/svix-server/src/main.rs
index 58dd08d6b..2399ec85f 100644
--- a/server/svix-server/src/main.rs
+++ b/server/svix-server/src/main.rs
@@ -5,21 +5,15 @@
 #![forbid(unsafe_code)]
 
 use dotenv::dotenv;
-use opentelemetry::runtime::Tokio;
-use opentelemetry_otlp::WithExportConfig;
 use std::process::exit;
 use svix_server::cfg::DefaultSignatureType;
 use svix_server::core::types::{EndpointSecretInternal, EndpointSecretType, OrganizationId};
 use svix_server::db::wipe_org;
-use tracing_subscriber::layer::SubscriberExt;
-use tracing_subscriber::util::SubscriberInitExt;
 use validator::Validate;
 
 use svix_server::core::security::{default_org_id, generate_org_token};
 
-use svix_server::{cfg, db, run};
-
-const CRATE_NAME: &str = env!("CARGO_CRATE_NAME");
+use svix_server::{cfg, db, run, setup_tracing};
 
 use clap::{Parser, Subcommand};
 
@@ -100,7 +94,7 @@ enum AsymmetricKeyCommands {
 
 fn signature_type_parser(s: &str) -> Result<DefaultSignatureType, String> {
     // XXX A bit hacky, but it's fine since an issue will just fail serde immediately after
-    let type_ = format!("\"{}\"", s);
+    let type_ = format!("\"{s}\"");
     serde_json::from_str(&type_).map_err(|x| x.to_string())
 }
 
@@ -117,75 +111,7 @@ async fn main() {
     let args = Args::parse();
     let cfg = cfg::load().expect("Error loading configuration");
 
-    if std::env::var_os("RUST_LOG").is_none() {
-        let level = cfg.log_level.to_string();
-        let mut var = vec![
-            format!("{crate}={level}", crate = CRATE_NAME),
-            format!("tower_http={level}"),
-        ];
-
-        if cfg.db_tracing {
-            var.push(format!("sqlx={level}"));
-        }
-
-        std::env::set_var("RUST_LOG", var.join(","));
-    }
-
-    let otel_layer = cfg.opentelemetry_address.as_ref().map(|addr| {
-        // Configure the OpenTelemetry tracing layer
-        opentelemetry::global::set_text_map_propagator(
-            opentelemetry::sdk::propagation::TraceContextPropagator::new(),
-        );
-
-        let exporter = opentelemetry_otlp::new_exporter()
-            .tonic()
-            .with_endpoint(addr);
-
-        let tracer = opentelemetry_otlp::new_pipeline()
-            .tracing()
-            .with_exporter(exporter)
-            .with_trace_config(
-                opentelemetry::sdk::trace::config()
-                    .with_sampler(
-                        cfg.opentelemetry_sample_ratio
-                            .map(opentelemetry::sdk::trace::Sampler::TraceIdRatioBased)
-                            .unwrap_or(opentelemetry::sdk::trace::Sampler::AlwaysOn),
-                    )
-                    .with_resource(opentelemetry::sdk::Resource::new(vec![
-                        opentelemetry::KeyValue::new("service.name", "svix_server"),
-                    ])),
-            )
-            .install_batch(Tokio)
-            .unwrap();
-        tracing_opentelemetry::layer().with_tracer(tracer)
-    });
-
-    // Then initialize logging with an additional layer priting to stdout. This additional layer is
-    // either formatted normally or in JSON format
-    match cfg.log_format {
-        cfg::LogFormat::Default => {
-            let stdout_layer = tracing_subscriber::fmt::layer();
-            tracing_subscriber::Registry::default()
-                .with(otel_layer)
-                .with(stdout_layer)
-                .with(tracing_subscriber::EnvFilter::from_default_env())
-                .init();
-        }
-        cfg::LogFormat::Json => {
-            let fmt = tracing_subscriber::fmt::format().json().flatten_event(true);
-            let json_fields = tracing_subscriber::fmt::format::JsonFields::new();
-
-            let stdout_layer = tracing_subscriber::fmt::layer()
-                .event_format(fmt)
-                .fmt_fields(json_fields);
-
-            tracing_subscriber::Registry::default()
-                .with(otel_layer)
-                .with(stdout_layer)
-                .with(tracing_subscriber::EnvFilter::from_default_env())
-                .init();
-        }
-    };
+    setup_tracing(&cfg);
 
     if let Some(wait_for_seconds) = args.wait_for {
         let mut wait_for = Vec::with_capacity(2);
@@ -225,7 +151,7 @@ async fn main() {
             let org_id = org_id.unwrap_or_else(default_org_id);
             let token =
                 generate_org_token(&cfg.jwt_secret, org_id).expect("Error generating token");
-            println!("Token (Bearer): {}", token);
+            println!("Token (Bearer): {token}");
             exit(0);
         }
         Some(Commands::AsymmetricKey { command }) => match command {
diff --git a/server/svix-server/src/queue/redis.rs b/server/svix-server/src/queue/redis.rs
index 6c03164e2..b8738b508 100644
--- a/server/svix-server/src/queue/redis.rs
+++ b/server/svix-server/src/queue/redis.rs
@@ -33,7 +33,7 @@ use axum::async_trait;
 
 use chrono::Utc;
 use redis::{
-    streams::{StreamClaimReply, StreamId, StreamPendingReply, StreamReadOptions, StreamReadReply},
+    streams::{StreamClaimReply, StreamId, StreamReadOptions, StreamReadReply},
     Cmd, FromRedisValue, RedisResult, RedisWrite, ToRedisArgs,
 };
 use tokio::time::sleep;
@@ -295,9 +295,9 @@ async fn new_pair_inner(
     delayed_queue_name: &'static str,
     delayed_lock: &'static str,
 ) -> (TaskQueueProducer, TaskQueueConsumer) {
-    let main_queue_name = format!("{}{}", queue_prefix, main_queue_name);
-    let delayed_queue_name = format!("{}{}", queue_prefix, delayed_queue_name);
-    let delayed_lock = format!("{}{}", queue_prefix, delayed_lock);
+    let main_queue_name = format!("{queue_prefix}{main_queue_name}");
+    let delayed_queue_name = format!("{queue_prefix}{delayed_queue_name}");
+    let delayed_lock = format!("{queue_prefix}{delayed_lock}");
 
     // Create the stream and consumer group for the MAIN queue should it not already exist. The
     // consumer is created automatically upon use so it does not have to be created here.
@@ -343,18 +343,6 @@ async fn new_pair_inner(
     let mqn = main_queue_name.clone();
     let dqn = delayed_queue_name.clone();
 
-    // Clear stale [`QueueTask`]s once per startup (see #679 for context)
-    tokio::spawn({
-        let pool = pool.clone();
-        let main_queue_name = main_queue_name.clone();
-
-        async move {
-            if let Err(e) = clear_acked(pool, &main_queue_name).await {
-                tracing::error!("Error clearing ACKed queue items: {}", e);
-            }
-        }
-    });
-
     // Migrate v1 queues to v2 and v2 queues to v3 on a loop with exponential backoff.
     tokio::spawn({
         let pool = pool.clone();
@@ -601,46 +589,6 @@ impl TaskQueueReceive for RedisQueueConsumer {
     }
 }
 
-async fn clear_acked(pool: RedisPool, queue: &str) -> Result<()> {
-    let mut conn = ctx!(pool.get().await)?;
-
-    loop {
-        let xpending_resp: StreamPendingReply = ctx!(
-            conn.query_async(Cmd::xpending_count(queue, WORKERS_GROUP, "-", "+", 1))
-                .await
-        )?;
-
-        match xpending_resp {
-            StreamPendingReply::Empty => {}
-            StreamPendingReply::Data(resp) => {
-                // Get the smallest unACKed task that's been read, and know that any task with an ID
-                // less than that has already been ACKed
-                let min = resp.start_id;
-
-                // XTRIM with MINID trims all IDs *lower* than the given threshold, so it will not
-                // include the unACKed item in the pending entries list.
-                let mut cmd = redis::cmd("XTRIM");
-                cmd.arg(queue).arg("MINID").arg(min);
-
-                let _ = ctx!(conn.query_async(cmd).await)?;
-
-                return Ok(());
-            }
-        };
-
-        // If `XPENDING` returns an empty array, just loop every 30 seconds until it does return. It is
-        // likely that any high traffic server will have a non-empty return within a reasonable amount of
-        // time.
-        //
-        // With low traffic servers, it is assumed that the data use of stale tasks is a nonissue.
-        //
-        // While an empty array does mean that it is safe to delete all tasks at that point in time, as
-        // all have been ACKed, this may lead to data loss if there are additional servers running at the
-        // same time adding to the queue.
-        tokio::time::sleep(Duration::from_secs(30)).await;
-    }
-}
-
 async fn migrate_v2_to_v3_queues(pool: &mut PooledConnection<'_>) -> Result<()> {
     migrate_list_to_stream(pool, LEGACY_V2_MAIN, MAIN).await?;
     migrate_list_to_stream(pool, LEGACY_V2_PROCESSING, MAIN).await?;
@@ -741,10 +689,7 @@ pub mod tests {
     use std::{sync::Arc, time::Duration};
 
     use chrono::Utc;
-    use redis::{
-        streams::{StreamReadOptions, StreamReadReply},
-        Cmd,
-    };
+    use redis::{streams::StreamReadReply, Cmd};
 
     use super::{
         migrate_list, migrate_list_to_stream, migrate_sset, new_pair_inner, to_redis_key, Direction,
@@ -1065,72 +1010,6 @@ pub mod tests {
         p.ack(recv1).await.unwrap();
     }
 
-    #[tokio::test]
-    async fn test_clearing_acked() {
-        let queue_name = "{test}_clearing_acked";
-
-        let cfg = crate::cfg::load().unwrap();
-        let pool = get_pool(cfg).await;
-
-        // Ensure the key is empty before running the test to avoid junk data
-        {
-            let mut conn = pool.get().await.unwrap();
-            let _: () = conn.query_async(Cmd::del(queue_name)).await.unwrap();
-        }
-
-        // Setup
-        {
-            let (p, mut c) = new_pair_inner(
-                pool.clone(),
-                Duration::from_secs(10),
-                "",
-                queue_name,
-                "{test}_clearing_acked_delayed",
-                "{test}_clearing_acked_delayed",
-            )
-            .await;
-
-            // Add three tasks
-            p.send(QueueTask::HealthCheck, None).await.unwrap();
-            p.send(QueueTask::HealthCheck, None).await.unwrap();
-            p.send(QueueTask::HealthCheck, None).await.unwrap();
-
-            // ACK one
-            let qt = c.receive_all().await.unwrap();
-            p.ack(qt.into_iter().next().unwrap()).await.unwrap();
-
-            // Leave the other two pending
-            let _ = c.receive_all().await.unwrap();
-            let _ = c.receive_all().await.unwrap();
-        }
-
-        // Create a new pair so the task runs
-        let (_, _) = new_pair_inner(
-            pool.clone(),
-            // High enough there's no chance the pending entries background task cleans things up
-            Duration::from_secs(10),
-            "",
-            queue_name,
-            "{test}_clearing_acked_delayed",
-            "{test}_clearing_acked_delayed",
-        )
-        .await;
-
-        tokio::time::sleep(Duration::from_millis(200)).await;
-
-        // Assert there are only two entries total in the stream
-        let mut conn = pool.get().await.unwrap();
-        let res: StreamReadReply = conn
-            .query_async(Cmd::xread_options(
-                &[queue_name],
-                &[0],
-                &StreamReadOptions::default().noack().count(5),
-            ))
-            .await
-            .unwrap();
-        assert_eq!(res.keys[0].ids.len(), 2);
-    }
-
     #[tokio::test]
     async fn test_migrations() {
         let cfg = crate::cfg::load().unwrap();
@@ -1185,7 +1064,7 @@ pub mod tests {
                         to_redis_key(&TaskQueueDelivery {
                             id: num.to_string(),
                             task: Arc::new(QueueTask::MessageV1(MessageTask {
-                                msg_id: MessageId(format!("TestMessageID{}", num)),
+                                msg_id: MessageId(format!("TestMessageID{num}")),
                                 app_id: ApplicationId("TestApplicationID".to_owned()),
                                 endpoint_id: EndpointId("TestEndpointID".to_owned()),
                                 trigger_type: MessageAttemptTriggerType::Manual,
@@ -1204,7 +1083,7 @@ pub mod tests {
                         to_redis_key(&TaskQueueDelivery {
                             id: num.to_string(),
                             task: Arc::new(QueueTask::MessageV1(MessageTask {
-                                msg_id: MessageId(format!("TestMessageID{}", num)),
+                                msg_id: MessageId(format!("TestMessageID{num}")),
                                 app_id: ApplicationId("TestApplicationID".to_owned()),
                                 endpoint_id: EndpointId("TestEndpointID".to_owned()),
                                 trigger_type: MessageAttemptTriggerType::Manual,
@@ -1266,7 +1145,7 @@ pub mod tests {
             assert_eq!(
                 &*recv.task,
                 &QueueTask::MessageV1(MessageTask {
-                    msg_id: MessageId(format!("TestMessageID{}", num)),
+                    msg_id: MessageId(format!("TestMessageID{num}")),
                     app_id: ApplicationId("TestApplicationID".to_owned()),
                     endpoint_id: EndpointId("TestEndpointID".to_owned()),
                     trigger_type: MessageAttemptTriggerType::Manual,
@@ -1280,7 +1159,7 @@ pub mod tests {
             assert_eq!(
                 &*recv.task,
                 &QueueTask::MessageV1(MessageTask {
-                    msg_id: MessageId(format!("TestMessageID{}", num)),
+                    msg_id: MessageId(format!("TestMessageID{num}")),
                     app_id: ApplicationId("TestApplicationID".to_owned()),
                     endpoint_id: EndpointId("TestEndpointID".to_owned()),
                     trigger_type: MessageAttemptTriggerType::Manual,
@@ -1294,7 +1173,7 @@ pub mod tests {
             assert_eq!(
                 &*recv.task,
                 &QueueTask::MessageV1(MessageTask {
-                    msg_id: MessageId(format!("TestMessageID{}", num)),
+                    msg_id: MessageId(format!("TestMessageID{num}")),
                     app_id: ApplicationId("TestApplicationID".to_owned()),
                     endpoint_id: EndpointId("TestEndpointID".to_owned()),
                     trigger_type: MessageAttemptTriggerType::Manual,
diff --git a/server/svix-server/src/v1/endpoints/application.rs b/server/svix-server/src/v1/endpoints/application.rs
index 16204fd56..fed8a0128 100644
--- a/server/svix-server/src/v1/endpoints/application.rs
+++ b/server/svix-server/src/v1/endpoints/application.rs
@@ -4,11 +4,12 @@
 use crate::{
     core::{
         permissions,
-        types::{ApplicationId, ApplicationIdOrUid, ApplicationUid},
+        types::{metadata::Metadata, ApplicationId, ApplicationIdOrUid, ApplicationUid},
     },
     ctx,
-    db::models::application,
-    error::Result,
+    db::models::{application, applicationmetadata},
+    error::{HttpError, Result},
+    transaction,
     v1::utils::{
         patch::{
             patch_field_non_nullable, patch_field_nullable, UnrequiredField,
@@ -26,13 +27,13 @@ use axum::{
 };
 use chrono::{DateTime, Utc};
 use hyper::StatusCode;
-use sea_orm::{entity::prelude::*, ActiveValue::Set, QueryOrder};
-use sea_orm::{ActiveModelTrait, DatabaseConnection, QuerySelect};
+use sea_orm::ActiveValue::Set;
+use sea_orm::{ActiveModelTrait, DatabaseConnection};
 use serde::{Deserialize, Serialize};
-use svix_server_derive::{ModelIn, ModelOut};
+use svix_server_derive::ModelOut;
 use validator::{Validate, ValidationError};
 
-#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Validate, ModelIn)]
+#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Validate)]
 #[serde(rename_all = "camelCase")]
 pub struct ApplicationIn {
     #[validate(
@@ -48,26 +49,31 @@ pub struct ApplicationIn {
     #[validate]
     #[serde(skip_serializing_if = "Option::is_none")]
     pub uid: Option<ApplicationUid>,
+
+    #[serde(default)]
+    pub metadata: Metadata,
 }
 
 // FIXME: This can and should be a derive macro
 impl ModelIn for ApplicationIn {
-    type ActiveModel = application::ActiveModel;
+    type ActiveModel = (application::ActiveModel, applicationmetadata::ActiveModel);
 
-    fn update_model(self, model: &mut Self::ActiveModel) {
+    fn update_model(self, (app, app_metadata): &mut Self::ActiveModel) {
         let ApplicationIn {
             name,
             rate_limit,
             uid,
+            metadata,
         } = self;
 
-        model.name = Set(name);
-        model.rate_limit = Set(rate_limit.map(|x| x.into()));
-        model.uid = Set(uid);
+        app.name = Set(name);
+        app.rate_limit = Set(rate_limit.map(|x| x.into()));
+        app.uid = Set(uid);
+        app_metadata.data = Set(metadata);
     }
 }
 
-#[derive(Deserialize, ModelIn, Serialize, Validate)]
+#[derive(Deserialize, Serialize, Validate)]
 #[serde(rename_all = "camelCase")]
 pub struct ApplicationPatch {
     #[serde(default, skip_serializing_if = "UnrequiredField::is_absent")]
@@ -84,24 +90,30 @@ pub struct ApplicationPatch {
     #[serde(default, skip_serializing_if = "UnrequiredNullableField::is_absent")]
     #[validate]
     pub uid: UnrequiredNullableField<ApplicationUid>,
+
+    #[serde(default, skip_serializing_if = "UnrequiredField::is_absent")]
+    pub metadata: UnrequiredField<Metadata>,
 }
 
 impl ModelIn for ApplicationPatch {
-    type ActiveModel = application::ActiveModel;
+    type ActiveModel = (application::ActiveModel, applicationmetadata::ActiveModel);
 
-    fn update_model(self, model: &mut Self::ActiveModel) {
+    fn update_model(self, (app, app_metadata): &mut Self::ActiveModel) {
         let ApplicationPatch {
             name,
             rate_limit,
             uid,
+            metadata,
         } = self;
 
         // `model`'s version of `rate_limit` is an i32, while `self`'s is a u16.
         let rate_limit_map = |x: u16| -> i32 { x.into() };
+        let data = metadata;
 
-        patch_field_non_nullable!(model, name);
-        patch_field_nullable!(model, rate_limit, rate_limit_map);
-        patch_field_nullable!(model, uid);
+        patch_field_non_nullable!(app, name);
+        patch_field_nullable!(app, rate_limit, rate_limit_map);
+        patch_field_nullable!(app, uid);
+        patch_field_non_nullable!(app_metadata, data);
     }
 }
 
@@ -154,19 +166,19 @@ pub struct ApplicationOut {
     pub id: ApplicationId,
     pub created_at: DateTime<Utc>,
     pub updated_at: DateTime<Utc>,
+    pub metadata: Metadata,
 }
 
-// FIXME: This can and should be a derive macro
-impl From<application::Model> for ApplicationOut {
-    fn from(model: application::Model) -> Self {
+impl From<(application::Model, applicationmetadata::Model)> for ApplicationOut {
+    fn from((app, metadata): (application::Model, applicationmetadata::Model)) -> Self {
         Self {
-            uid: model.uid,
-            name: model.name,
-            rate_limit: model.rate_limit.map(|x| x as u16),
-
-            id: model.id,
-            created_at: model.created_at.into(),
-            updated_at: model.updated_at.into(),
+            uid: app.uid,
+            name: app.name,
+            rate_limit: app.rate_limit.map(|x| x as u16),
+            id: app.id,
+            created_at: app.created_at.into(),
+            updated_at: app.updated_at.into(),
+            metadata: metadata.metadata(),
         }
     }
 }
@@ -179,19 +191,13 @@ async fn list_applications(
     let PaginationLimit(limit) = pagination.limit;
     let iterator = pagination.iterator.clone();
 
-    let mut query = application::Entity::secure_find(org_id)
-        .order_by_asc(application::Column::Id)
-        .limit(limit + 1);
+    let apps =
+        ctx!(application::Model::fetch_many_with_metadata(db, org_id, limit + 1, iterator).await)?;
 
-    if let Some(iterator) = iterator {
-        query = query.filter(application::Column::Id.gt(iterator))
-    }
+    let results = apps.map(ApplicationOut::from).collect();
 
     Ok(Json(ApplicationOut::list_response_no_prev(
-        ctx!(query.all(db).await)?
-            .into_iter()
-            .map(|x| x.into())
-            .collect(),
+        results,
         limit as usize,
     )))
 }
@@ -208,83 +214,104 @@ pub struct CreateApplicationQuery {
 
 async fn create_application(
     Extension(ref db): Extension<DatabaseConnection>,
-    ValidatedJson(data): ValidatedJson<ApplicationIn>,
     query: ValidatedQuery<CreateApplicationQuery>,
     permissions::Organization { org_id }: permissions::Organization,
+    ValidatedJson(data): ValidatedJson<ApplicationIn>,
 ) -> Result<(StatusCode, Json<ApplicationOut>)> {
-    if query.get_if_exists {
-        if let Some(ref uid) = data.uid {
-            let app = ctx!(
-                application::Entity::secure_find(org_id.clone())
-                    .filter(application::Column::Uid.eq(uid.to_owned()))
-                    .one(db)
-                    .await
-            )?;
-            if let Some(ret) = app {
-                return Ok((StatusCode::OK, Json(ret.into())));
+    if let Some(ref uid) = data.uid {
+        if let Some((app, metadata)) = ctx!(
+            application::Model::fetch_with_metadata(db, org_id.clone(), uid.clone().into()).await
+        )? {
+            if query.get_if_exists {
+                return Ok((StatusCode::OK, Json((app, metadata).into())));
             }
-        }
+            return Err(HttpError::conflict(
+                None,
+                Some("An application with that id or uid already exists".into()),
+            )
+            .into());
+        };
     }
 
-    let app = application::ActiveModel {
-        org_id: Set(org_id),
-        ..data.into()
-    };
-    let ret = ctx!(app.insert(db).await)?;
-    Ok((StatusCode::CREATED, Json(ret.into())))
+    let app = application::ActiveModel::new(org_id.clone());
+    let metadata = applicationmetadata::ActiveModel::new(app.id.clone().unwrap(), None);
+
+    let mut model = (app, metadata);
+    data.update_model(&mut model);
+    let (app, metadata) = model;
+
+    let (app, metadata) = transaction!(db, |txn| async move {
+        let app_result = ctx!(app.insert(txn).await)?;
+        let metadata = ctx!(metadata.upsert_or_delete(txn).await)?;
+        Ok((app_result, metadata))
+    })?;
+
+    Ok((StatusCode::CREATED, Json((app, metadata).into())))
 }
 
 async fn get_application(
-    permissions::Application { app }: permissions::Application,
+    permissions::ApplicationWithMetadata { app, metadata }: permissions::ApplicationWithMetadata,
 ) -> Result<Json<ApplicationOut>> {
-    Ok(Json(app.into()))
+    Ok(Json((app, metadata).into()))
 }
 
 async fn update_application(
     Extension(ref db): Extension<DatabaseConnection>,
-    ValidatedJson(data): ValidatedJson<ApplicationIn>,
     Path(app_id): Path<ApplicationIdOrUid>,
     permissions::Organization { org_id }: permissions::Organization,
+    ValidatedJson(data): ValidatedJson<ApplicationIn>,
 ) -> Result<(StatusCode, Json<ApplicationOut>)> {
-    let app = ctx!(
-        application::Entity::secure_find_by_id_or_uid(org_id.clone(), app_id)
-            .one(db)
-            .await
-    )?;
-
-    match app {
-        Some(app) => {
-            let mut app: application::ActiveModel = app.into();
-            data.update_model(&mut app);
-            let ret = ctx!(app.update(db).await)?;
-
-            Ok((StatusCode::OK, Json(ret.into())))
-        }
-        None => {
-            let ret = ctx!(
-                application::ActiveModel {
-                    org_id: Set(org_id),
-                    ..data.into()
-                }
-                .insert(db)
-                .await
-            )?;
-
-            Ok((StatusCode::CREATED, Json(ret.into())))
-        }
-    }
+    let (app, metadata, create_models) = if let Some((app, metadata)) =
+        ctx!(application::Model::fetch_with_metadata(db, org_id.clone(), app_id).await)?
+    {
+        (app.into(), metadata.into(), false)
+    } else {
+        let app = application::ActiveModel::new(org_id);
+        let metadata = applicationmetadata::ActiveModel::new(app.id.clone().unwrap(), None);
+        (app, metadata, true)
+    };
+
+    let mut models = (app, metadata);
+    data.update_model(&mut models);
+    let (app, metadata) = models;
+
+    let (app, metadata) = transaction!(db, |txn| async move {
+        let app = if create_models {
+            ctx!(app.insert(txn).await)?
+        } else {
+            ctx!(app.update(txn).await)?
+        };
+        let metadata = ctx!(metadata.upsert_or_delete(txn).await)?;
+        Ok((app, metadata))
+    })?;
+
+    let status = if create_models {
+        StatusCode::CREATED
+    } else {
+        StatusCode::OK
+    };
+    Ok((status, Json((app, metadata).into())))
 }
 
 async fn patch_application(
     Extension(ref db): Extension<DatabaseConnection>,
-    ValidatedJson(data): ValidatedJson<ApplicationPatch>,
     permissions::OrganizationWithApplication { app }: permissions::OrganizationWithApplication,
+    ValidatedJson(data): ValidatedJson<ApplicationPatch>,
 ) -> Result<Json<ApplicationOut>> {
-    let mut app: application::ActiveModel = app.into();
-    data.update_model(&mut app);
+    let metadata = ctx!(app.fetch_or_create_metadata(db).await)?;
+    let app: application::ActiveModel = app.into();
+
+    let mut model = (app, metadata);
+    data.update_model(&mut model);
+    let (app, metadata) = model;
+
+    let (app, metadata) = transaction!(db, |txn| async move {
+        let app = ctx!(app.update(txn).await)?;
+        let metadata = ctx!(metadata.upsert_or_delete(txn).await)?;
+        Ok((app, metadata))
+    })?;
 
-    let ret = ctx!(app.update(db).await)?;
-    Ok(Json(ret.into()))
+    Ok(Json((app, metadata).into()))
 }
 
 async fn delete_application(
diff --git a/server/svix-server/src/v1/endpoints/attempt.rs b/server/svix-server/src/v1/endpoints/attempt.rs
index bd4b66bae..51bf278c7 100644
--- a/server/svix-server/src/v1/endpoints/attempt.rs
+++ b/server/svix-server/src/v1/endpoints/attempt.rs
@@ -697,30 +697,41 @@ async fn resend_webhook(
 }
 
 pub fn router() -> Router {
-    Router::new().nest(
-        "/app/:app_id/",
-        Router::new()
-            .nest(
-                "/msg/:msg_id",
-                Router::new()
-                    // NOTE: [`list_messageattempts`] is deprecated
-                    .route("/attempt/", get(list_messageattempts))
-                    .route("/attempt/:attempt_id/", get(get_messageattempt))
-                    .route("/endpoint/", get(list_attempted_destinations))
-                    .route("/endpoint/:endp_id/resend/", post(resend_webhook))
-                    // NOTE: [`list_attempts_for_endpoint`] is deprecated
-                    .route(
-                        "/endpoint/:endp_id/attempt/",
-                        get(list_attempts_for_endpoint),
-                    ),
-            )
-            .route("/endpoint/:endp_id/msg/", get(list_attempted_messages))
-            .route(
-                "/attempt/endpoint/:endp_id/",
-                get(list_attempts_by_endpoint),
-            )
-            .route("/attempt/msg/:msg_id/", get(list_attempts_by_msg)),
-    )
+    Router::new()
+        // NOTE: [`list_messageattempts`] is deprecated
+        .route(
+            "/app/:app_id/msg/:msg_id/attempt/",
+            get(list_messageattempts),
+        )
+        .route(
+            "/app/:app_id/msg/:msg_id/attempt/:attempt_id/",
+            get(get_messageattempt),
+        )
+        .route(
+            "/app/:app_id/msg/:msg_id/endpoint/",
+            get(list_attempted_destinations),
+        )
+        .route(
+            "/app/:app_id/msg/:msg_id/endpoint/:endp_id/resend/",
+            post(resend_webhook),
+        )
+        // NOTE: [`list_attempts_for_endpoint`] is deprecated
+        .route(
+            "/app/:app_id/msg/:msg_id/endpoint/:endp_id/attempt/",
+            get(list_attempts_for_endpoint),
+        )
+        .route(
+            "/app/:app_id/endpoint/:endp_id/msg/",
+            get(list_attempted_messages),
+        )
+        .route(
+            "/app/:app_id/attempt/endpoint/:endp_id/",
+            get(list_attempts_by_endpoint),
+        )
+        .route(
+            "/app/:app_id/attempt/msg/:msg_id/",
+            get(list_attempts_by_msg),
+        )
 }
 
 #[cfg(test)]
diff --git a/server/svix-server/src/v1/endpoints/endpoint/crud.rs b/server/svix-server/src/v1/endpoints/endpoint/crud.rs
index b00422dc7..a576361b8 100644
--- a/server/svix-server/src/v1/endpoints/endpoint/crud.rs
+++ b/server/svix-server/src/v1/endpoints/endpoint/crud.rs
@@ -60,8 +60,8 @@ pub(super) async fn create_endpoint(
     Extension(ref db): Extension<DatabaseConnection>,
     Extension(cfg): Extension<Configuration>,
     Extension(op_webhooks): Extension<OperationalWebhookSender>,
-    ValidatedJson(data): ValidatedJson<EndpointIn>,
     permissions::Application { app }: permissions::Application,
+    ValidatedJson(data): ValidatedJson<EndpointIn>,
 ) -> Result<(StatusCode, Json<EndpointOut>)> {
     if let Some(ref event_types_ids) = data.event_types_ids {
         validate_event_types(db, event_types_ids, &app.org_id).await?;
@@ -123,8 +123,8 @@ pub(super) async fn update_endpoint(
     Extension(cfg): Extension<Configuration>,
     Extension(op_webhooks): Extension<OperationalWebhookSender>,
     Path((_app_id, endp_id)): Path<(ApplicationIdOrUid, EndpointIdOrUid)>,
-    ValidatedJson(data): ValidatedJson<EndpointIn>,
     permissions::Application { app }: permissions::Application,
+    ValidatedJson(data): ValidatedJson<EndpointIn>,
 ) -> Result<(StatusCode, Json<EndpointOut>)> {
     let endp = ctx!(
         endpoint::Entity::secure_find_by_id_or_uid(app.id.clone(), endp_id)
@@ -203,8 +203,8 @@ pub(super) async fn patch_endpoint(
     Extension(cfg): Extension<Configuration>,
     Extension(op_webhooks): Extension<OperationalWebhookSender>,
     Path((_app_id, endp_id)): Path<(ApplicationIdOrUid, EndpointIdOrUid)>,
-    ValidatedJson(data): ValidatedJson<EndpointPatch>,
     permissions::Application { app }: permissions::Application,
+    ValidatedJson(data): ValidatedJson<EndpointPatch>,
 ) -> Result<Json<EndpointOut>> {
     let endp = ctx!(
         endpoint::Entity::secure_find_by_id_or_uid(app.id.clone(), endp_id)
@@ -317,7 +317,7 @@ async fn validate_event_types(
     } else {
         Err(HttpError::unprocessable_entity(vec![ValidationErrorItem {
             loc: vec!["body".to_owned(), "event_types_ids".to_owned()],
-            msg: format!("The following type names don't exist: {:?}", missing),
+            msg: format!("The following type names don't exist: {missing:?}"),
             ty: "value_error".to_owned(),
         }])
         .into())
diff --git a/server/svix-server/src/v1/endpoints/endpoint/headers.rs b/server/svix-server/src/v1/endpoints/endpoint/headers.rs
index cbf72943b..cc4c8e605 100644
--- a/server/svix-server/src/v1/endpoints/endpoint/headers.rs
+++ b/server/svix-server/src/v1/endpoints/endpoint/headers.rs
@@ -38,8 +38,8 @@ pub(super) async fn get_endpoint_headers(
 pub(super) async fn update_endpoint_headers(
     Extension(ref db): Extension<DatabaseConnection>,
     Path((_app_id, endp_id)): Path<(ApplicationIdOrUid, EndpointIdOrUid)>,
-    ValidatedJson(data): ValidatedJson<EndpointHeadersIn>,
     permissions::Application { app }: permissions::Application,
+    ValidatedJson(data): ValidatedJson<EndpointHeadersIn>,
 ) -> Result<(StatusCode, Json<EmptyResponse>)> {
     let endp = ctx!(
         endpoint::Entity::secure_find_by_id_or_uid(app.id.clone(), endp_id)
@@ -58,8 +58,8 @@ pub(super) async fn update_endpoint_headers(
 pub(super) async fn patch_endpoint_headers(
     Extension(ref db): Extension<DatabaseConnection>,
     Path((_app_id, endp_id)): Path<(ApplicationIdOrUid, EndpointIdOrUid)>,
-    ValidatedJson(data): ValidatedJson<EndpointHeadersPatchIn>,
     permissions::Application { app }: permissions::Application,
+    ValidatedJson(data): ValidatedJson<EndpointHeadersPatchIn>,
 ) -> Result<(StatusCode, Json<EmptyResponse>)> {
     let endp = ctx!(
         endpoint::Entity::secure_find_by_id_or_uid(app.id.clone(), endp_id)
diff --git a/server/svix-server/src/v1/endpoints/endpoint/mod.rs b/server/svix-server/src/v1/endpoints/endpoint/mod.rs
index 25b8b02cc..c276916c6 100644
--- a/server/svix-server/src/v1/endpoints/endpoint/mod.rs
+++ b/server/svix-server/src/v1/endpoints/endpoint/mod.rs
@@ -393,10 +393,10 @@ impl EndpointHeadersOut {
 
 impl From<EndpointHeaders> for EndpointHeadersOut {
     fn from(hdr: EndpointHeaders) -> Self {
-        let (sens, remaining) = hdr
-            .0
-            .into_iter()
-            .partition(|(k, _)| Self::SENSITIVE_HEADERS.iter().any(|&x| x == k));
+        let (sens, remaining) = hdr.0.into_iter().partition(|(k, _)| {
+            let k = k.to_lowercase();
+            Self::SENSITIVE_HEADERS.iter().any(|&x| x == k)
+        });
 
         Self {
             headers: remaining,
@@ -494,52 +494,53 @@ async fn endpoint_stats(
 }
 
 pub fn router() -> Router {
-    Router::new().nest(
-        "/app/:app_id",
-        Router::new()
-            .route(
-                "/endpoint/",
-                post(crud::create_endpoint).get(crud::list_endpoints),
-            )
-            .route(
-                "/endpoint/:endp_id/",
-                get(crud::get_endpoint)
-                    .put(crud::update_endpoint)
-                    .patch(crud::patch_endpoint)
-                    .delete(crud::delete_endpoint),
-            )
-            .route(
-                "/endpoint/:endp_id/secret/",
-                get(secrets::get_endpoint_secret),
-            )
-            .route(
-                "/endpoint/:endp_id/secret/rotate/",
-                post(secrets::rotate_endpoint_secret),
-            )
-            .route("/endpoint/:endp_id/stats/", get(endpoint_stats))
-            .route(
-                "/endpoint/:endp_id/send-example/",
-                post(api_not_implemented),
-            )
-            .route(
-                "/endpoint/:endp_id/recover/",
-                post(recovery::recover_failed_webhooks),
-            )
-            .route(
-                "/endpoint/:endp_id/headers/",
-                get(headers::get_endpoint_headers)
-                    .patch(headers::patch_endpoint_headers)
-                    .put(headers::update_endpoint_headers),
-            ),
-    )
+    Router::new()
+        .route(
+            "/app/:app_id/endpoint/",
+            post(crud::create_endpoint).get(crud::list_endpoints),
+        )
+        .route(
+            "/app/:app_id/endpoint/:endp_id/",
+            get(crud::get_endpoint)
+                .put(crud::update_endpoint)
+                .patch(crud::patch_endpoint)
+                .delete(crud::delete_endpoint),
+        )
+        .route(
+            "/app/:app_id/endpoint/:endp_id/secret/",
+            get(secrets::get_endpoint_secret),
+        )
+        .route(
+            "/app/:app_id/endpoint/:endp_id/secret/rotate/",
+            post(secrets::rotate_endpoint_secret),
+        )
+        .route("/app/:app_id/endpoint/:endp_id/stats/", get(endpoint_stats))
+        .route(
+            "/app/:app_id/endpoint/:endp_id/send-example/",
+            post(api_not_implemented),
+        )
+        .route(
+            "/app/:app_id/endpoint/:endp_id/recover/",
+            post(recovery::recover_failed_webhooks),
+        )
+        .route(
+            "/app/:app_id/endpoint/:endp_id/headers/",
+            get(headers::get_endpoint_headers)
+                .patch(headers::patch_endpoint_headers)
+                .put(headers::update_endpoint_headers),
+        )
 }
 
 #[cfg(test)]
 mod tests {
 
-    use super::{validate_url, EndpointHeadersIn, EndpointHeadersPatchIn, EndpointIn};
+    use crate::core::types::EndpointHeaders;
+
+    use super::{
+        validate_url, EndpointHeadersIn, EndpointHeadersOut, EndpointHeadersPatchIn, EndpointIn,
+    };
     use serde_json::json;
-    use std::collections::HashMap;
+    use std::collections::{HashMap, HashSet};
     use validator::Validate;
 
     const URL_VALID: &str = "https://www.example.com";
@@ -629,6 +630,26 @@ mod tests {
         valid.validate().unwrap();
     }
 
+    #[test]
+    fn test_endpoint_headers_sensitive() {
+        let headers = EndpointHeaders(HashMap::from([
+            ("foo".to_string(), "1".to_string()),
+            ("authorization".to_string(), "test".to_string()),
+            ("X-Auth-Token".to_string(), "test2".to_string()),
+        ]));
+
+        let headers_out: EndpointHeadersOut = headers.into();
+
+        assert_eq!(
+            headers_out.headers,
+            HashMap::from([("foo".to_string(), "1".to_string())])
+        );
+        assert_eq!(
+            headers_out.sensitive,
+            HashSet::from(["authorization".to_string(), "X-Auth-Token".to_string()])
+        );
+    }
+
     #[test]
     fn test_endpoint_headers_patch_in_validation() {
         let headers_valid = HashMap::from([("x-valid", "1")]);
diff --git a/server/svix-server/src/v1/endpoints/endpoint/recovery.rs b/server/svix-server/src/v1/endpoints/endpoint/recovery.rs
index 94bb93903..bcdbb2dba 100644
--- a/server/svix-server/src/v1/endpoints/endpoint/recovery.rs
+++ b/server/svix-server/src/v1/endpoints/endpoint/recovery.rs
@@ -77,8 +77,8 @@ pub(super) async fn recover_failed_webhooks(
     Extension(ref db): Extension<DatabaseConnection>,
     Extension(queue_tx): Extension<TaskQueueProducer>,
     Path((_app_id, endp_id)): Path<(ApplicationIdOrUid, EndpointIdOrUid)>,
-    ValidatedJson(data): ValidatedJson<RecoverIn>,
     permissions::Application { app }: permissions::Application,
+    ValidatedJson(data): ValidatedJson<RecoverIn>,
 ) -> Result<(StatusCode, Json<EmptyResponse>)> {
     // Add five minutes so that people can easily just do `now() - two_weeks` without having to worry about clock sync
     let timeframe = chrono::Duration::days(14);
diff --git a/server/svix-server/src/v1/endpoints/endpoint/secrets.rs b/server/svix-server/src/v1/endpoints/endpoint/secrets.rs
index 0c05ef85c..66cc5c242 100644
--- a/server/svix-server/src/v1/endpoints/endpoint/secrets.rs
+++ b/server/svix-server/src/v1/endpoints/endpoint/secrets.rs
@@ -53,8 +53,8 @@ pub(super) async fn rotate_endpoint_secret(
     Extension(ref db): Extension<DatabaseConnection>,
     Extension(cfg): Extension<Configuration>,
     Path((_app_id, endp_id)): Path<(ApplicationIdOrUid, EndpointIdOrUid)>,
-    ValidatedJson(data): ValidatedJson<EndpointSecretRotateIn>,
     permissions::Application { app }: permissions::Application,
+    ValidatedJson(data): ValidatedJson<EndpointSecretRotateIn>,
 ) -> Result<(StatusCode, Json<EmptyResponse>)> {
     let mut endp = ctx!(
         endpoint::Entity::secure_find_by_id_or_uid(app.id, endp_id)
diff --git a/server/svix-server/src/v1/endpoints/event_type.rs b/server/svix-server/src/v1/endpoints/event_type.rs
index b8f73b493..0d8552023 100644
--- a/server/svix-server/src/v1/endpoints/event_type.rs
+++ b/server/svix-server/src/v1/endpoints/event_type.rs
@@ -207,8 +207,8 @@ async fn list_event_types(
 
 async fn create_event_type(
     Extension(ref db): Extension<DatabaseConnection>,
-    ValidatedJson(data): ValidatedJson<EventTypeIn>,
     permissions::Organization { org_id }: permissions::Organization,
+    ValidatedJson(data): ValidatedJson<EventTypeIn>,
 ) -> Result<(StatusCode, Json<EventTypeOut>)> {
     let evtype = ctx!(
         eventtype::Entity::secure_find_by_name(org_id.clone(), data.name.to_owned())
@@ -258,8 +258,8 @@ async fn get_event_type(
 async fn update_event_type(
     Extension(ref db): Extension<DatabaseConnection>,
     Path(evtype_name): Path<EventTypeName>,
-    ValidatedJson(data): ValidatedJson<EventTypeUpdate>,
     permissions::Organization { org_id }: permissions::Organization,
+    ValidatedJson(data): ValidatedJson<EventTypeUpdate>,
 ) -> Result<(StatusCode, Json<EventTypeOut>)> {
     let evtype = ctx!(
         eventtype::Entity::secure_find_by_name(org_id.clone(), evtype_name.clone())
@@ -294,8 +294,8 @@ async fn update_event_type(
 async fn patch_event_type(
     Extension(ref db): Extension<DatabaseConnection>,
     Path(evtype_name): Path<EventTypeName>,
-    ValidatedJson(data): ValidatedJson<EventTypePatch>,
     permissions::Organization { org_id }: permissions::Organization,
+    ValidatedJson(data): ValidatedJson<EventTypePatch>,
 ) -> Result<Json<EventTypeOut>> {
     let evtype = ctx!(
         eventtype::Entity::secure_find_by_name(org_id, evtype_name)
@@ -358,8 +358,8 @@ async fn get_retry_schedule(
 async fn update_retry_schedule(
     Extension(ref db): Extension<DatabaseConnection>,
     Path(evtype_name): Path<EventTypeName>,
-    ValidatedJson(data): ValidatedJson<RetryScheduleInOut>,
     permissions::Organization { org_id }: permissions::Organization,
+    ValidatedJson(data): ValidatedJson<RetryScheduleInOut>,
 ) -> Result<Json<RetryScheduleInOut>> {
     let evtype = ctx!(
         eventtype::Entity::secure_find_by_name(org_id, evtype_name)
diff --git a/server/svix-server/src/v1/endpoints/message.rs b/server/svix-server/src/v1/endpoints/message.rs
index 2ba7a7f9e..847377823 100644
--- a/server/svix-server/src/v1/endpoints/message.rs
+++ b/server/svix-server/src/v1/endpoints/message.rs
@@ -225,15 +225,15 @@ async fn create_message(
     ValidatedQuery(CreateMessageQueryParams { with_content }): ValidatedQuery<
         CreateMessageQueryParams,
     >,
-    ValidatedJson(data): ValidatedJson<MessageIn>,
     permissions::OrganizationWithApplication { app }: permissions::OrganizationWithApplication,
+    ValidatedJson(data): ValidatedJson<MessageIn>,
 ) -> Result<(StatusCode, Json<MessageOut>)> {
     let create_message_app = CreateMessageApp::layered_fetch(
         cache,
         db,
         Some(app.clone()),
-        app.id.clone(),
         app.org_id.clone(),
+        app.id.clone(),
         std::time::Duration::from_secs(30),
     )
     .await?
@@ -249,7 +249,7 @@ async fn create_message(
 
     let trigger_type = MessageAttemptTriggerType::Scheduled;
     if !create_message_app
-        .filtered_endpoints(trigger_type, &msg)
+        .filtered_endpoints(trigger_type, &msg.event_type, msg.channels.as_ref())
         .is_empty()
     {
         queue_tx
@@ -299,12 +299,9 @@ async fn get_message(
 }
 
 pub fn router() -> Router {
-    Router::new().nest(
-        "/app/:app_id",
-        Router::new()
-            .route("/msg/", post(create_message).get(list_messages))
-            .route("/msg/:msg_id/", get(get_message)),
-    )
+    Router::new()
+        .route("/app/:app_id/msg/", post(create_message).get(list_messages))
+        .route("/app/:app_id/msg/:msg_id/", get(get_message))
 }
 
 #[cfg(test)]
diff --git a/server/svix-server/src/v1/mod.rs b/server/svix-server/src/v1/mod.rs
index 32dcbc5f4..239814a41 100644
--- a/server/svix-server/src/v1/mod.rs
+++ b/server/svix-server/src/v1/mod.rs
@@ -25,12 +25,8 @@ pub fn router() -> Router {
 
 #[cfg(debug_assertions)]
 mod development {
-    use axum::{
-        async_trait,
-        extract::{FromRequest, RequestParts},
-        routing::get,
-        Json, Router,
-    };
+    use axum::{async_trait, extract::FromRequestParts, routing::get, Json, Router};
+    use http::request::Parts;
 
     use crate::error::{Error, Result};
     use crate::v1::utils::EmptyResponse;
@@ -40,14 +36,14 @@ mod development {
     }
 
     #[async_trait]
-    impl<B> FromRequest<B> for EchoData
+    impl<S> FromRequestParts<S> for EchoData
     where
-        B: Send,
+        S: Send + Sync,
     {
         type Rejection = Error;
 
-        async fn from_request(req: &mut RequestParts<B>) -> Result<Self> {
-            let headers = format!("{:?}", req.headers());
+        async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> {
+            let headers = format!("{:?}", parts.headers);
             Ok(EchoData { headers })
         }
     }
diff --git a/server/svix-server/src/v1/utils/mod.rs b/server/svix-server/src/v1/utils/mod.rs
index a072ad5d9..3851e963a 100644
--- a/server/svix-server/src/v1/utils/mod.rs
+++ b/server/svix-server/src/v1/utils/mod.rs
@@ -6,10 +6,11 @@ use std::{borrow::Cow, collections::HashSet, error::Error as StdError, ops::Dere
 use axum::{
     async_trait,
     body::HttpBody,
-    extract::{FromRequest, Query, RequestParts},
+    extract::{FromRequest, FromRequestParts, Query},
     BoxError,
 };
 use chrono::{DateTime, Utc};
+use http::{request::Parts, Request};
 use regex::Regex;
 use sea_orm::{ColumnTrait, QueryFilter, QueryOrder, QuerySelect};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
@@ -161,6 +162,7 @@ pub struct EmptyResponse {}
 pub struct ListResponse<T: Clone> {
     pub data: Vec<T>,
     pub iterator: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub prev_iterator: Option<String>,
     pub done: bool,
 }
@@ -262,7 +264,7 @@ fn validation_errors(
                     .flat_map(|(k, v)| {
                         // Add the list index to the location
                         let mut loc = loc.clone();
-                        loc.push(format!("[{}]", k));
+                        loc.push(format!("[{k}]"));
 
                         validation_errors(loc, *v)
                     })
@@ -276,17 +278,18 @@ fn validation_errors(
 pub struct ValidatedJson<T>(pub T);
 
 #[async_trait]
-impl<T, B> FromRequest<B> for ValidatedJson<T>
+impl<T, S, B> FromRequest<S, B> for ValidatedJson<T>
 where
     T: DeserializeOwned + Validate,
-    B: HttpBody + Send,
+    S: Send + Sync,
+    B: HttpBody + Send + 'static,
     B::Data: Send,
     B::Error: Into<BoxError>,
 {
     type Rejection = Error;
 
-    async fn from_request(req: &mut RequestParts<B>) -> Result<Self> {
-        let b = bytes::Bytes::from_request(req).await.map_err(|e| {
+    async fn from_request(req: Request<B>, state: &S) -> Result<Self> {
+        let b = bytes::Bytes::from_request(req, state).await.map_err(|e| {
             tracing::error!("Error reading body as bytes: {}", e);
             HttpError::internal_server_error(None, Some("Failed to read request body".to_owned()))
         })?;
@@ -324,17 +327,15 @@ where
 pub struct ValidatedQuery<T>(pub T);
 
 #[async_trait]
-impl<T, B> FromRequest<B> for ValidatedQuery<T>
+impl<T, S> FromRequestParts<S> for ValidatedQuery<T>
 where
     T: DeserializeOwned + Validate,
-    B: HttpBody + Send,
-    B::Data: Send,
-    B::Error: Into<BoxError>,
+    S: Send + Sync,
 {
     type Rejection = Error;
 
-    async fn from_request(req: &mut RequestParts<B>) -> Result<Self> {
-        let Query(value) = Query::<T>::from_request(req)
+    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> {
+        let Query(value) = Query::<T>::from_request_parts(parts, state)
             .await
             .map_err(|err| HttpError::bad_request(None, Some(err.to_string())))?;
         value.validate().map_err(|e| {
@@ -360,15 +361,15 @@ pub struct MessageListFetchOptions {
 }
 
 #[async_trait]
-impl<B> FromRequest<B> for MessageListFetchOptions
+impl<S> FromRequestParts<S> for MessageListFetchOptions
 where
-    B: Send,
+    S: Send + Sync,
 {
     type Rejection = Error;
 
-    async fn from_request(req: &mut RequestParts<B>) -> Result<Self> {
+    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> {
         let pairs: Vec<(String, String)> =
-            serde_urlencoded::from_str(req.uri().query().unwrap_or_default())
+            serde_urlencoded::from_str(parts.uri.query().unwrap_or_default())
                 .map_err(|err| HttpError::bad_request(None, Some(err.to_string())))?;
 
         let mut before = None;
diff --git a/server/svix-server/src/wait_for.rs b/server/svix-server/src/wait_for.rs
index 49257477c..4c4637c14 100644
--- a/server/svix-server/src/wait_for.rs
+++ b/server/svix-server/src/wait_for.rs
@@ -6,11 +6,11 @@ pub async fn wait_for_dsn(
     default_port: u16,
     wait_for_seconds: u64,
 ) {
-    let dsn = url::Url::parse(dsn).unwrap_or_else(|_| panic!("Invalid {} DSN", dependency_name));
+    let dsn = url::Url::parse(dsn).unwrap_or_else(|_| panic!("Invalid {dependency_name} DSN"));
 
     let host = dsn
         .host()
-        .unwrap_or_else(|| panic!("Expected {} host", dependency_name));
+        .unwrap_or_else(|| panic!("Expected {dependency_name} host"));
     let port = dsn.port().unwrap_or(default_port);
 
     let sleep = tokio::time::sleep(Duration::from_secs(wait_for_seconds));
@@ -27,7 +27,7 @@ pub async fn wait_for_dsn(
             }
 
             _ = &mut sleep => {
-                panic!("Waiting for host={} port={} timed out", host, port);
+                panic!("Waiting for host={host} port={port} timed out");
             }
         }
     }
diff --git a/server/svix-server/src/worker.rs b/server/svix-server/src/worker.rs
index 3cb9357c6..68b764c15 100644
--- a/server/svix-server/src/worker.rs
+++ b/server/svix-server/src/worker.rs
@@ -67,7 +67,7 @@ kv_def!(FailureCacheKey, FailureCacheValue, "SVIX_FAILURE_CACHE");
 
 impl FailureCacheKey {
     pub fn new(app_id: &ApplicationId, endp_id: &EndpointId) -> FailureCacheKey {
-        FailureCacheKey(format!("_{}_{}", app_id, endp_id))
+        FailureCacheKey(format!("_{app_id}_{endp_id}"))
     }
 }
 
@@ -85,7 +85,7 @@ kv_def!(
 
 impl RetryScheduleCacheKey {
     pub fn new(org_id: &OrganizationId, event_type_name: &EventTypeName) -> RetryScheduleCacheKey {
-        RetryScheduleCacheKey(format!("_{}_{}", org_id, event_type_name))
+        RetryScheduleCacheKey(format!("_{org_id}_{event_type_name}"))
     }
 }
 
@@ -170,7 +170,7 @@ fn sign_msg(
     msg_id: &MessageId,
     endpoint_signing_keys: &[&EndpointSecretInternal],
 ) -> String {
-    let to_sign = format!("{}.{}.{}", msg_id, timestamp, body);
+    let to_sign = format!("{msg_id}.{timestamp}.{body}");
     endpoint_signing_keys
         .iter()
         .map(|x| {
@@ -386,7 +386,7 @@ async fn dispatch(
                     tracing::warn!("Error reading response body: {}", err);
                     messageattempt::ActiveModel {
                         response_status_code: Set(status_code),
-                        response: Set(format!("failed to read response body: {}", err)),
+                        response: Set(format!("failed to read response body: {err}")),
                         status: Set(status),
                         ..attempt
                     }
@@ -652,8 +652,8 @@ async fn process_task(worker_context: WorkerContext<'_>, queue_task: Arc<QueueTa
         cache.clone(),
         db,
         None,
-        msg.app_id.clone(),
         msg.org_id.clone(),
+        msg.app_id.clone(),
         Duration::from_secs(30),
     )
     .await?
@@ -662,7 +662,7 @@ async fn process_task(worker_context: WorkerContext<'_>, queue_task: Arc<QueueTa
     let app_uid = create_message_app.uid.clone();
 
     let endpoints: Vec<CreateMessageEndpoint> = create_message_app
-        .filtered_endpoints(*trigger_type, &msg)
+        .filtered_endpoints(*trigger_type, &msg.event_type, msg.channels.as_ref())
         .iter()
         .filter(|endpoint| match &*queue_task {
             QueueTask::HealthCheck => unreachable!(),
@@ -942,7 +942,7 @@ mod tests {
             &[&test_key],
         );
 
-        let to_sign = format!("{}.{}.{}", msg_id, timestamp, body);
+        let to_sign = format!("{msg_id}.{timestamp}.{body}");
         assert!(signatures.starts_with("v1a,"));
         let sig: Signature = Signature::from_slice(
             base64::decode(&signatures["v1a,".len()..])
@@ -970,7 +970,7 @@ mod tests {
             &[&test_key],
         );
 
-        let to_sign = format!("{}.{}.{}", msg_id, timestamp, body);
+        let to_sign = format!("{msg_id}.{timestamp}.{body}");
         let sig_arry: Vec<&str> = signatures.split(' ').collect();
         let v1b = sig_arry[0];
         assert!(v1b.starts_with("v1b,"));
diff --git a/yarn.lock b/yarn.lock
index ded674b74..d5bcd3e83 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -274,7 +274,7 @@ compute-lcm@^1.1.2:
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+  integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
 concurrently@6.4.0:
   version "6.4.0"
@@ -618,9 +618,9 @@ mimic-fn@^2.1.0:
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
 minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
   dependencies:
     brace-expansion "^1.1.7"
 


Test patch:
diff --git a/server/run-tests.sh b/server/run-tests.sh
index 928d618aa..b318e70a4 100755
--- a/server/run-tests.sh
+++ b/server/run-tests.sh
@@ -7,6 +7,7 @@ TEST_COMMAND="cargo test --all --all-features --all-targets"
 # Common variables:
 export DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
 export SVIX_JWT_SECRET="test value"
+export SVIX_LOG_LEVEL="info"
 
 echo "*********** RUN 1 ***********"
 SVIX_QUEUE_TYPE="redis" \
diff --git a/server/svix-server/tests/e2e_application.rs b/server/svix-server/tests/e2e_application.rs
index 101f87a57..db1ac3280 100644
--- a/server/svix-server/tests/e2e_application.rs
+++ b/server/svix-server/tests/e2e_application.rs
@@ -1,8 +1,8 @@
 // SPDX-FileCopyrightText: © 2022 Svix Authors
 // SPDX-License-Identifier: MIT
 
+use crate::utils::common_calls::metadata;
 use reqwest::StatusCode;
-
 use svix_server::{
     cfg::CacheType, core::types::ApplicationUid, v1::endpoints::application::ApplicationIn,
     v1::endpoints::application::ApplicationOut,
@@ -61,13 +61,14 @@ async fn test_patch() {
     // Assert that no other field was changed
     assert_eq!(out.rate_limit, None);
     assert_eq!(out.uid, None);
+    assert_eq!(out.metadata, metadata("{}"));
 
     // Test that rate_limit may be set while the rest are omitted
     let _: ApplicationOut = client
         .patch(
             &format!("api/v1/app/{}/", app.id),
             serde_json::json! ({
-                "rateLimit": 1
+                "rateLimit": 1,
             }),
             StatusCode::OK,
         )
@@ -145,6 +146,31 @@ async fn test_patch() {
     // Assert that no other field was changed
     assert_eq!(out.name, "second_name".to_owned());
     assert_eq!(out.rate_limit, None);
+
+    // Test that metadata may be changed while the rest are omitted
+    let _: ApplicationOut = client
+        .patch(
+            &format!("api/v1/app/{}/", app.id),
+            serde_json::json!({
+                "metadata": {
+                    "foo": "bar",
+                    "bizz": "baz",
+                },
+            }),
+            StatusCode::OK,
+        )
+        .await
+        .unwrap();
+
+    // Assert the change was made
+    let out = client
+        .get::<ApplicationOut>(&format!("api/v1/app/{}/", app.id), StatusCode::OK)
+        .await
+        .unwrap();
+    assert_eq!(metadata(r#"{"foo": "bar", "bizz": "baz"}"#), out.metadata);
+    // Assert that no other field was changed
+    assert_eq!(out.name, "second_name".to_owned());
+    assert_eq!(out.rate_limit, None);
 }
 
 #[tokio::test]
@@ -198,7 +224,7 @@ async fn test_crud() {
     let app_1_id = app_1.id;
     let app_1: ApplicationOut = client
         .put(
-            &format!("api/v1/app/{}/", app_1_id),
+            &format!("api/v1/app/{app_1_id}/"),
             application_in(APP_NAME_1_2),
             StatusCode::OK,
         )
@@ -208,7 +234,7 @@ async fn test_crud() {
     let app_2_id = app_2.id;
     let app_2: ApplicationOut = client
         .put(
-            &format!("api/v1/app/{}/", app_2_id),
+            &format!("api/v1/app/{app_2_id}/"),
             application_in(APP_NAME_2_2),
             StatusCode::OK,
         )
@@ -218,7 +244,7 @@ async fn test_crud() {
     // CONFIRM UPDATE
     assert_eq!(
         client
-            .get::<ApplicationOut>(&format!("api/v1/app/{}/", app_1_id), StatusCode::OK,)
+            .get::<ApplicationOut>(&format!("api/v1/app/{app_1_id}/"), StatusCode::OK,)
             .await
             .unwrap(),
         app_1
@@ -226,7 +252,7 @@ async fn test_crud() {
 
     assert_eq!(
         client
-            .get::<ApplicationOut>(&format!("api/v1/app/{}/", app_2_id), StatusCode::OK,)
+            .get::<ApplicationOut>(&format!("api/v1/app/{app_2_id}/"), StatusCode::OK,)
             .await
             .unwrap(),
         app_2
@@ -251,6 +277,66 @@ async fn test_crud() {
         .get(&format!("api/v1/app/{}/", app_2.id), StatusCode::NOT_FOUND)
         .await
         .unwrap();
+
+    let app: ApplicationOut = client
+        .post(
+            "api/v1/app/",
+            serde_json::json!({
+                "name": "Apps all around",
+            }),
+            StatusCode::CREATED,
+        )
+        .await
+        .unwrap();
+
+    assert_eq!(app.metadata, metadata(r#"{}"#));
+
+    let updated: ApplicationOut = client
+        .patch(
+            &format!("api/v1/app/{}/", app.id),
+            serde_json::json!({
+                "metadata": {
+                    "bizz": "bar"
+                },
+            }),
+            StatusCode::OK,
+        )
+        .await
+        .unwrap();
+    assert_eq!(updated.metadata, metadata(r#"{"bizz":"bar"}"#));
+
+    let new_app: ApplicationOut = client
+        .put(
+            "api/v1/app/one_upserted_boi/",
+            serde_json::json!({
+                "name": "Apps for two",
+                "metadata": {
+                    "foo": "bar"
+                },
+            }),
+            StatusCode::CREATED,
+        )
+        .await
+        .unwrap();
+
+    assert_eq!(new_app.metadata, metadata(r#"{"foo":"bar"}"#));
+
+    let updated_metadata_app: ApplicationOut = client
+        .put(
+            &format!("api/v1/app/{}/", new_app.id),
+            serde_json::json!({
+                "name": "New Name",
+                "metadata": {
+                    "new": "data"
+                },
+            }),
+            StatusCode::OK,
+        )
+        .await
+        .unwrap();
+
+    assert_eq!(updated_metadata_app.metadata, metadata(r#"{"new":"data"}"#));
+    assert_eq!(updated_metadata_app.name, "New Name");
 }
 
 #[tokio::test]
@@ -504,6 +590,11 @@ async fn test_get_or_create() {
             ApplicationIn {
                 name: "App 1".to_owned(),
                 uid: Some(ApplicationUid("app1".to_owned())),
+                metadata: metadata(
+                    r#"{
+                    "foo": "bar"
+                }"#,
+                ),
                 ..Default::default()
             },
             StatusCode::CREATED,
@@ -511,6 +602,15 @@ async fn test_get_or_create() {
         .await
         .unwrap();
 
+    assert_eq!(
+        app.metadata,
+        metadata(
+            r#"{
+        "foo": "bar"
+    }"#,
+        )
+    );
+
     let _: IgnoredResponse = client
         .post(
             "api/v1/app/",
diff --git a/server/svix-server/tests/e2e_attempt.rs b/server/svix-server/tests/e2e_attempt.rs
index f1d27d069..fa0b4d27d 100644
--- a/server/svix-server/tests/e2e_attempt.rs
+++ b/server/svix-server/tests/e2e_attempt.rs
@@ -46,7 +46,7 @@ async fn test_list_attempted_messages() {
     endp2.uid = Some(EndpointUid("test".to_owned()));
     let endp_id_2 = client
         .post::<EndpointIn, EndpointOut>(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             endp2,
             StatusCode::CREATED,
         )
@@ -67,14 +67,14 @@ async fn test_list_attempted_messages() {
     run_with_retries(|| async {
         let list_1: ListResponse<AttemptedMessageOut> = client
             .get(
-                &format!("api/v1/app/{}/endpoint/{}/msg/", app_id, endp_id_1),
+                &format!("api/v1/app/{app_id}/endpoint/{endp_id_1}/msg/"),
                 StatusCode::OK,
             )
             .await
             .unwrap();
         let list_2: ListResponse<AttemptedMessageOut> = client
             .get(
-                &format!("api/v1/app/{}/endpoint/{}/msg/", app_id, endp_id_2),
+                &format!("api/v1/app/{app_id}/endpoint/{endp_id_2}/msg/"),
                 StatusCode::OK,
             )
             .await
@@ -140,7 +140,7 @@ async fn test_list_attempts_by_endpoint() {
         for endp_id in [endp_id_1.clone(), endp_id_2.clone()] {
             let list: ListResponse<MessageAttemptOut> = client
                 .get(
-                    &format!("api/v1/app/{}/attempt/endpoint/{}/", app_id, endp_id),
+                    &format!("api/v1/app/{app_id}/attempt/endpoint/{endp_id}/"),
                     StatusCode::OK,
                 )
                 .await
@@ -158,14 +158,14 @@ async fn test_list_attempts_by_endpoint() {
 
     let list_1: ListResponse<MessageAttemptOut> = client
         .get(
-            &format!("api/v1/app/{}/attempt/endpoint/{}/", app_id, endp_id_1),
+            &format!("api/v1/app/{app_id}/attempt/endpoint/{endp_id_1}/"),
             StatusCode::OK,
         )
         .await
         .unwrap();
     let list_2: ListResponse<MessageAttemptOut> = client
         .get(
-            &format!("api/v1/app/{}/attempt/endpoint/{}/", app_id, endp_id_2),
+            &format!("api/v1/app/{app_id}/attempt/endpoint/{endp_id_2}/"),
             StatusCode::OK,
         )
         .await
@@ -763,7 +763,7 @@ async fn test_pagination_forward_and_back() {
 
     while !done {
         let iter_suffix = if let Some(iter) = iterator {
-            format!("&iterator={}", iter)
+            format!("&iterator={iter}")
         } else {
             String::new()
         };
@@ -793,7 +793,7 @@ async fn test_pagination_forward_and_back() {
 
     while !done {
         let iter_suffix = if let Some(iter) = prev_iterator {
-            format!("&iterator={}", iter)
+            format!("&iterator={iter}")
         } else {
             String::new()
         };
diff --git a/server/svix-server/tests/e2e_auth.rs b/server/svix-server/tests/e2e_auth.rs
index 921f01621..8795f0335 100644
--- a/server/svix-server/tests/e2e_auth.rs
+++ b/server/svix-server/tests/e2e_auth.rs
@@ -17,7 +17,7 @@ use utils::{common_calls::application_in, start_svix_server, IgnoredResponse, Te
 async fn dashboard_access(org_client: &TestClient, application_id: &ApplicationId) -> TestClient {
     let resp: DashboardAccessOut = org_client
         .post(
-            &format!("api/v1/auth/dashboard-access/{}/", application_id),
+            &format!("api/v1/auth/dashboard-access/{application_id}/"),
             (),
             StatusCode::OK,
         )
@@ -68,14 +68,14 @@ async fn test_restricted_application_access() {
         .unwrap();
     let _: IgnoredResponse = client
         .put(
-            &format!("api/v1/app/{}", app_id),
+            &format!("api/v1/app/{app_id}/"),
             application_in("TEST_APP_NAME"),
             StatusCode::FORBIDDEN,
         )
         .await
         .unwrap();
     let _: IgnoredResponse = client
-        .delete(&format!("api/v1/app/{}", app_id), StatusCode::FORBIDDEN)
+        .delete(&format!("api/v1/app/{app_id}/"), StatusCode::FORBIDDEN)
         .await
         .unwrap();
     let _: IgnoredResponse = client
@@ -85,11 +85,11 @@ async fn test_restricted_application_access() {
 
     // READ should succeed when accessing the app_id the token is auhtorized for but no others
     let _: IgnoredResponse = client
-        .get(&format!("api/v1/app/{}", app_id_2), StatusCode::NOT_FOUND)
+        .get(&format!("api/v1/app/{app_id_2}/"), StatusCode::NOT_FOUND)
         .await
         .unwrap();
     let _: ApplicationOut = client
-        .get(&format!("api/v1/app/{}", app_id), StatusCode::OK)
+        .get(&format!("api/v1/app/{app_id}/"), StatusCode::OK)
         .await
         .unwrap();
 }
diff --git a/server/svix-server/tests/e2e_endpoint.rs b/server/svix-server/tests/e2e_endpoint.rs
index 357745ae5..fa9e9041a 100644
--- a/server/svix-server/tests/e2e_endpoint.rs
+++ b/server/svix-server/tests/e2e_endpoint.rs
@@ -61,7 +61,7 @@ async fn get_endpoint(
 ) -> Result<EndpointOut> {
     client
         .get(
-            &format!("api/v1/app/{}/endpoint/{}/", app_id, ep_id),
+            &format!("api/v1/app/{app_id}/endpoint/{ep_id}/"),
             StatusCode::OK,
         )
         .await
@@ -74,7 +74,7 @@ async fn get_endpoint_404(
 ) -> Result<IgnoredResponse> {
     client
         .get(
-            &format!("api/v1/app/{}/endpoint/{}/", app_id, ep_id),
+            &format!("api/v1/app/{app_id}/endpoint/{ep_id}/"),
             StatusCode::NOT_FOUND,
         )
         .await
@@ -83,7 +83,7 @@ async fn get_endpoint_404(
 async fn delete_endpoint(client: &TestClient, app_id: &ApplicationId, ep_id: &str) -> Result<()> {
     let _: IgnoredResponse = client
         .delete(
-            &format!("api/v1/app/{}/endpoint/{}/", app_id, ep_id),
+            &format!("api/v1/app/{app_id}/endpoint/{ep_id}/"),
             StatusCode::NO_CONTENT,
         )
         .await?;
@@ -103,7 +103,7 @@ async fn test_patch() {
         .unwrap()
         .id;
 
-    let url = format!("api/v1/app/{}/endpoint/{}/", app, ep);
+    let url = format!("api/v1/app/{app}/endpoint/{ep}/");
 
     // Test that the description may be set
     let _: EndpointOut = client
@@ -326,7 +326,7 @@ async fn test_patch() {
     // But first make an event type to set it to
     let _: EventTypeOut = client
         .post(
-            "api/v1/event-type",
+            "api/v1/event-type/",
             serde_json::json!({
                 "description": "a test event type",
                 "name": "test",
@@ -533,7 +533,7 @@ async fn test_crud() {
     let app_1_ep_1_id = app_1_ep_1.id;
     let app_1_ep_1: EndpointOut = client
         .put(
-            &format!("api/v1/app/{}/endpoint/{}/", app_1, app_1_ep_1_id),
+            &format!("api/v1/app/{app_1}/endpoint/{app_1_ep_1_id}/"),
             endpoint_in(EP_URI_APP_1_EP_1_VER_2),
             StatusCode::OK,
         )
@@ -544,7 +544,7 @@ async fn test_crud() {
     // Test that PUT with an invalid ID creates an endpoint
     let app_1_ep_3: EndpointOut = client
         .put(
-            &format!("api/v1/app/{}/endpoint/fake-id/", app_1),
+            &format!("api/v1/app/{app_1}/endpoint/fake-id/"),
             endpoint_in(EP_URI_APP_1_EP_1_VER_2),
             StatusCode::CREATED,
         )
@@ -651,7 +651,7 @@ async fn test_uid() {
 
     client
         .post::<_, IgnoredResponse>(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             ep_2,
             StatusCode::CONFLICT,
         )
@@ -802,7 +802,7 @@ async fn test_recovery_should_fail_if_start_time_too_old() {
 
     let _: serde_json::Value = client
         .post(
-            &format!("api/v1/app/{}/endpoint/{}/recover/", app_id, endp_id),
+            &format!("api/v1/app/{app_id}/endpoint/{endp_id}/recover/"),
             RecoverIn {
                 since: Utc::now() - chrono::Duration::weeks(3),
             },
@@ -851,7 +851,7 @@ async fn test_recovery_expected_retry_counts() {
     recover_webhooks(
         &client,
         after_msg,
-        &format!("api/v1/app/{}/endpoint/{}/recover/", app_id, endp_id),
+        &format!("api/v1/app/{app_id}/endpoint/{endp_id}/recover/"),
     )
     .await;
 
@@ -863,7 +863,7 @@ async fn test_recovery_expected_retry_counts() {
     recover_webhooks(
         &client,
         before_msg,
-        &format!("api/v1/app/{}/endpoint/{}/recover/", app_id, endp_id),
+        &format!("api/v1/app/{app_id}/endpoint/{endp_id}/recover/"),
     )
     .await;
 
@@ -888,7 +888,7 @@ async fn test_endpoint_rotate_max() {
     for _ in 0..ExpiringSigningKeys::MAX_OLD_KEYS {
         let _: IgnoredResponse = client
             .post(
-                &format!("api/v1/app/{}/endpoint/{}/secret/rotate/", app_id, endp_id),
+                &format!("api/v1/app/{app_id}/endpoint/{endp_id}/secret/rotate/"),
                 serde_json::json!({ "key": null }),
                 StatusCode::NO_CONTENT,
             )
@@ -898,7 +898,7 @@ async fn test_endpoint_rotate_max() {
 
     let _: IgnoredResponse = client
         .post(
-            &format!("api/v1/app/{}/endpoint/{}/secret/rotate/", app_id, endp_id),
+            &format!("api/v1/app/{app_id}/endpoint/{endp_id}/secret/rotate/"),
             serde_json::json!({ "key": null }),
             StatusCode::BAD_REQUEST,
         )
@@ -1352,7 +1352,7 @@ async fn test_invalid_endpoint_secret() {
 
         let _: IgnoredResponse = client
             .post(
-                &format!("api/v1/app/{}/endpoint/", app_id),
+                &format!("api/v1/app/{app_id}/endpoint/"),
                 ep_in,
                 StatusCode::UNPROCESSABLE_ENTITY,
             )
@@ -1508,7 +1508,7 @@ async fn test_endpoint_filter_events() {
 
     let _ep_with_empty_events: IgnoredResponse = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             ep_empty_events,
             StatusCode::UNPROCESSABLE_ENTITY,
         )
@@ -1517,7 +1517,7 @@ async fn test_endpoint_filter_events() {
 
     let _ep_with_nonexistent_event: IgnoredResponse = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             ep_with_events.to_owned(),
             StatusCode::UNPROCESSABLE_ENTITY,
         )
@@ -1526,7 +1526,7 @@ async fn test_endpoint_filter_events() {
 
     let _et: EventTypeOut = client
         .post(
-            "api/v1/event-type",
+            "api/v1/event-type/",
             event_type_in("et1", None).unwrap(),
             StatusCode::CREATED,
         )
@@ -1535,7 +1535,7 @@ async fn test_endpoint_filter_events() {
 
     let ep_with_valid_event: EndpointOut = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             ep_with_events.to_owned(),
             StatusCode::CREATED,
         )
@@ -1607,7 +1607,7 @@ async fn test_endpoint_filter_channels() {
 
     let _ep_w_empty_channel: IgnoredResponse = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             ep_empty_channels,
             StatusCode::UNPROCESSABLE_ENTITY,
         )
@@ -1616,7 +1616,7 @@ async fn test_endpoint_filter_channels() {
 
     let ep_with_channel: EndpointOut = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             ep_with_channels.to_owned(),
             StatusCode::CREATED,
         )
@@ -1718,7 +1718,7 @@ async fn test_msg_event_types_filter() {
         event_type_in("et2", None).unwrap(),
     ] {
         let _: EventTypeOut = client
-            .post("api/v1/event-type", et, StatusCode::CREATED)
+            .post("api/v1/event-type/", et, StatusCode::CREATED)
             .await
             .unwrap();
     }
@@ -1826,7 +1826,7 @@ async fn test_msg_channels_filter() {
 
         let msg: MessageOut = client
             .get(
-                &format!("api/v1/app/{}/msg/{}", &app_id, &msg.id),
+                &format!("api/v1/app/{}/msg/{}/", &app_id, &msg.id),
                 StatusCode::OK,
             )
             .await
@@ -1855,7 +1855,7 @@ async fn test_endpoint_headers_manipulation() {
 
     let _: IgnoredResponse = client
         .patch(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             patched_headers_in,
             StatusCode::NO_CONTENT,
         )
@@ -1864,7 +1864,7 @@ async fn test_endpoint_headers_manipulation() {
 
     let recvd_headers: EndpointHeadersOut = client
         .get(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             StatusCode::OK,
         )
         .await
@@ -1889,7 +1889,7 @@ async fn test_endpoint_headers_manipulation() {
     ] {
         let _: IgnoredResponse = client
             .put(
-                &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+                &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
                 serde_json::json!({ "headers": { bad_hdr: "123"}}),
                 StatusCode::UNPROCESSABLE_ENTITY,
             )
@@ -1914,7 +1914,7 @@ async fn test_endpoint_headers_manipulation() {
     for hdrs in [&org_headers, &updated_headers] {
         let _: IgnoredResponse = client
             .put(
-                &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+                &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
                 hdrs,
                 StatusCode::NO_CONTENT,
             )
@@ -1923,7 +1923,7 @@ async fn test_endpoint_headers_manipulation() {
 
         let recvd_headers: EndpointHeadersOut = client
             .get(
-                &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+                &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
                 StatusCode::OK,
             )
             .await
@@ -1941,7 +1941,7 @@ async fn test_endpoint_headers_manipulation() {
 
     let _: IgnoredResponse = client
         .patch(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             &patched_headers_in,
             StatusCode::NO_CONTENT,
         )
@@ -1950,7 +1950,7 @@ async fn test_endpoint_headers_manipulation() {
 
     let recvd_headers: EndpointHeadersOut = client
         .get(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             StatusCode::OK,
         )
         .await
@@ -1973,7 +1973,7 @@ async fn test_endpoint_headers_manipulation() {
 
     let _: IgnoredResponse = client
         .put(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             redacted_headers,
             StatusCode::NO_CONTENT,
         )
@@ -1982,7 +1982,7 @@ async fn test_endpoint_headers_manipulation() {
 
     let recvd_headers: EndpointHeadersOut = client
         .get(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             StatusCode::OK,
         )
         .await
@@ -2020,7 +2020,7 @@ async fn test_endpoint_headers_sending() {
 
     let _: IgnoredResponse = client
         .put(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             &headers,
             StatusCode::NO_CONTENT,
         )
@@ -2059,7 +2059,7 @@ async fn test_endpoint_header_key_capitalization() {
 
     let _: IgnoredResponse = client
         .put(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             &headers,
             StatusCode::NO_CONTENT,
         )
@@ -2068,7 +2068,7 @@ async fn test_endpoint_header_key_capitalization() {
 
     let retrieved_headers: EndpointHeadersOut = client
         .get(
-            &format!("api/v1/app/{}/endpoint/{}/headers", app_id, endp.id),
+            &format!("api/v1/app/{}/endpoint/{}/headers/", app_id, endp.id),
             StatusCode::OK,
         )
         .await
@@ -2095,7 +2095,7 @@ async fn test_endpoint_https_only() {
 
     let _endpoint: EndpointOut = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             endpoint_in(https_url),
             StatusCode::CREATED,
         )
@@ -2104,7 +2104,7 @@ async fn test_endpoint_https_only() {
 
     let _endpoint: EndpointOut = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             endpoint_in(http_url),
             StatusCode::CREATED,
         )
@@ -2122,7 +2122,7 @@ async fn test_endpoint_https_only() {
 
     let _endpoint: EndpointOut = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             endpoint_in(https_url),
             StatusCode::CREATED,
         )
@@ -2131,7 +2131,7 @@ async fn test_endpoint_https_only() {
 
     let _: IgnoredResponse = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             endpoint_in(http_url),
             StatusCode::UNPROCESSABLE_ENTITY,
         )
diff --git a/server/svix-server/tests/e2e_event_type.rs b/server/svix-server/tests/e2e_event_type.rs
index aa05d67d1..560ca03dc 100644
--- a/server/svix-server/tests/e2e_event_type.rs
+++ b/server/svix-server/tests/e2e_event_type.rs
@@ -29,7 +29,7 @@ async fn test_patch() {
 
     let et: EventTypeOut = client
         .post(
-            "api/v1/event-type",
+            "api/v1/event-type/",
             event_type_in(
                 "test-event-type",
                 serde_json::json!({
@@ -54,7 +54,7 @@ async fn test_patch() {
     // Test that PUT with invalid ID creates an event type
     let _: EventTypeOut = client
         .put(
-            "api/v1/event-type/fake-id",
+            "api/v1/event-type/fake-id/",
             event_type_in("test-event-type", None).unwrap(),
             StatusCode::CREATED,
         )
@@ -163,7 +163,7 @@ async fn test_event_type_create_read_list() {
 
     let et: EventTypeOut = client
         .post(
-            "api/v1/event-type",
+            "api/v1/event-type/",
             event_type_in("test-event-type", None).unwrap(),
             StatusCode::CREATED,
         )
@@ -186,7 +186,7 @@ async fn test_event_type_create_read_list() {
     assert!(list.data.contains(&et));
 
     let list: ListResponse<EventTypeOut> = client
-        .get("api/v1/event-type", StatusCode::OK)
+        .get("api/v1/event-type/", StatusCode::OK)
         .await
         .unwrap();
     assert_eq!(list.data.len(), 1);
@@ -218,7 +218,7 @@ async fn test_retry_schedule_crud() {
 
     let _: EventTypeOut = client
         .post(
-            "api/v1/event-type",
+            "api/v1/event-type/",
             event_type_in(event_type_name, None).unwrap(),
             StatusCode::CREATED,
         )
@@ -229,7 +229,7 @@ async fn test_retry_schedule_crud() {
 
     let rsi: RetryScheduleInOut = client
         .put(
-            &format!("api/v1/event-type/{}/retry-schedule/", event_type_name),
+            &format!("api/v1/event-type/{event_type_name}/retry-schedule/"),
             retry_schedule_payload,
             StatusCode::OK,
         )
@@ -238,7 +238,7 @@ async fn test_retry_schedule_crud() {
 
     let rso: RetryScheduleInOut = client
         .get(
-            &format!("api/v1/event-type/{}/retry-schedule", event_type_name),
+            &format!("api/v1/event-type/{event_type_name}/retry-schedule/"),
             StatusCode::OK,
         )
         .await
@@ -255,7 +255,7 @@ async fn test_retry_schedule_override() {
 
     let _: EventTypeOut = client
         .post(
-            "api/v1/event-type",
+            "api/v1/event-type/",
             event_type_in(event_type_name, None).unwrap(),
             StatusCode::CREATED,
         )
@@ -264,7 +264,7 @@ async fn test_retry_schedule_override() {
 
     let _: RetryScheduleInOut = client
         .put(
-            &format!("api/v1/event-type/{}/retry-schedule/", event_type_name),
+            &format!("api/v1/event-type/{event_type_name}/retry-schedule/"),
             serde_json::json!({ "retrySchedule": vec![1, 1, 1] }),
             StatusCode::OK,
         )
@@ -277,7 +277,7 @@ async fn test_retry_schedule_override() {
 
     let _: EndpointOut = client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             serde_json::json!({
                 "url": bad_url.clone(),
                 "version": 1,
@@ -311,7 +311,7 @@ async fn test_schema() {
     let (client, _jh) = start_svix_server().await;
     let _: serde_json::Value = client
         .post(
-            "api/v1/event-type",
+            "api/v1/event-type/",
             serde_json::json!({
                             "name": "bad-schema",
                             "description": "I have a bad schema",
diff --git a/server/svix-server/tests/e2e_message.rs b/server/svix-server/tests/e2e_message.rs
index a2c36b40f..56169b80e 100644
--- a/server/svix-server/tests/e2e_message.rs
+++ b/server/svix-server/tests/e2e_message.rs
@@ -100,7 +100,7 @@ async fn test_message_create_read_list_with_content() {
 
     let msg_1_w_payload: MessageOut = client
         .post(
-            &format!("api/v1/app/{}/msg", &app_id),
+            &format!("api/v1/app/{}/msg/", &app_id),
             message_in(&app_id, msg_payload.clone()).unwrap(),
             StatusCode::ACCEPTED,
         )
@@ -156,7 +156,7 @@ async fn test_message_create_read_list_with_content() {
     }
 
     let list: ListResponse<MessageOut> = client
-        .get(&format!("api/v1/app/{}/msg", &app_id), StatusCode::OK)
+        .get(&format!("api/v1/app/{}/msg/", &app_id), StatusCode::OK)
         .await
         .unwrap();
     assert_eq!(list.data.len(), 2);
@@ -195,7 +195,7 @@ async fn test_failed_message_gets_recorded() {
 
     let msg_res: MessageOut = client
         .post(
-            &format!("api/v1/app/{}/msg", &app_id),
+            &format!("api/v1/app/{}/msg/", &app_id),
             message_in(&app_id, msg_payload.clone()).unwrap(),
             StatusCode::ACCEPTED,
         )
@@ -253,7 +253,7 @@ async fn test_mulitple_endpoints() {
 
     let msg_res: MessageOut = client
         .post(
-            &format!("api/v1/app/{}/msg", &app_id),
+            &format!("api/v1/app/{}/msg/", &app_id),
             message_in(&app_id, msg_payload.clone()).unwrap(),
             StatusCode::ACCEPTED,
         )
@@ -314,7 +314,7 @@ async fn test_failed_message_gets_requeued() {
 
     let msg_res: MessageOut = client
         .post(
-            &format!("api/v1/app/{}/msg", &app_id),
+            &format!("api/v1/app/{}/msg/", &app_id),
             message_in(&app_id, msg_payload.clone()).unwrap(),
             StatusCode::ACCEPTED,
         )
diff --git a/server/svix-server/tests/e2e_operational_webhooks.rs b/server/svix-server/tests/e2e_operational_webhooks.rs
index 77daf1cc3..2bfedb78e 100644
--- a/server/svix-server/tests/e2e_operational_webhooks.rs
+++ b/server/svix-server/tests/e2e_operational_webhooks.rs
@@ -13,8 +13,8 @@ use svix_server::{
     core::{
         security::{generate_org_token, management_org_id},
         types::{
-            ApplicationId, ApplicationUid, BaseId, EndpointId, EndpointUid, MessageAttemptId,
-            MessageId, MessageUid, OrganizationId,
+            metadata::Metadata, ApplicationId, ApplicationUid, BaseId, EndpointId, EndpointUid,
+            MessageAttemptId, MessageId, MessageUid, OrganizationId,
         },
     },
     v1::endpoints::{
@@ -141,6 +141,7 @@ async fn test_endpoint_create_update_and_delete() {
                 name: "TestOperationalWebhookApplication".to_owned(),
                 rate_limit: None,
                 uid: Some(ApplicationUid(org_id.to_string())),
+                metadata: Metadata::default(),
             },
             StatusCode::CREATED,
         )
@@ -287,6 +288,7 @@ async fn test_message_attempt_operational_webhooks() {
                 name: "TestOperationalWebhookApplication".to_owned(),
                 rate_limit: None,
                 uid: Some(ApplicationUid(org_id.to_string())),
+                metadata: Metadata::default(),
             },
             StatusCode::CREATED,
         )
diff --git a/server/svix-server/tests/queue.rs b/server/svix-server/tests/queue.rs
index 937704dce..7a4a04a0e 100644
--- a/server/svix-server/tests/queue.rs
+++ b/server/svix-server/tests/queue.rs
@@ -42,12 +42,9 @@ async fn test_many_queue_consumers_inner(prefix: &str, delay: Option<Duration>)
         let pool = get_pool(cfg.clone()).await;
         let mut conn = pool.get().await.unwrap();
 
-        conn.query_async::<()>(redis::Cmd::del(&format!(
-            "{}{{queue}}_svix_v3_main",
-            prefix
-        )))
-        .await
-        .unwrap();
+        conn.query_async::<()>(redis::Cmd::del(&format!("{prefix}{{queue}}_svix_v3_main")))
+            .await
+            .unwrap();
     }
 
     // Make 20 producers and 20 consumers using the same configuration
@@ -115,7 +112,7 @@ async fn test_many_queue_consumers_inner(prefix: &str, delay: Option<Duration>)
         out.append(&mut jh_out);
 
         if read < 5 {
-            panic!("Consumer starved, only read {} messages", read);
+            panic!("Consumer starved, only read {read} messages");
         }
     }
 
diff --git a/server/svix-server/tests/utils/common_calls.rs b/server/svix-server/tests/utils/common_calls.rs
index bce081d45..436a83795 100644
--- a/server/svix-server/tests/utils/common_calls.rs
+++ b/server/svix-server/tests/utils/common_calls.rs
@@ -9,7 +9,7 @@ use reqwest::StatusCode;
 
 use serde::{de::DeserializeOwned, Serialize};
 use svix_server::{
-    core::types::{ApplicationId, EventTypeName, MessageId},
+    core::types::{metadata::Metadata, ApplicationId, EventTypeName, MessageId},
     v1::{
         endpoints::{
             application::{ApplicationIn, ApplicationOut},
@@ -41,7 +41,7 @@ pub async fn create_test_app(client: &TestClient, name: &str) -> Result<Applicat
 
 pub async fn delete_test_app(client: &TestClient, id: ApplicationId) -> Result<IgnoredResponse> {
     client
-        .delete(&format!("api/v1/app/{}/", id), StatusCode::NO_CONTENT)
+        .delete(&format!("api/v1/app/{id}/"), StatusCode::NO_CONTENT)
         .await
 }
 
@@ -70,7 +70,7 @@ pub async fn post_endpoint(
 ) -> Result<EndpointOut> {
     client
         .post(
-            &format!("api/v1/app/{}/endpoint/", app_id),
+            &format!("api/v1/app/{app_id}/endpoint/"),
             ep,
             StatusCode::CREATED,
         )
@@ -85,7 +85,7 @@ pub async fn put_endpoint(
 ) -> Result<EndpointOut> {
     client
         .put(
-            &format!("api/v1/app/{}/endpoint/{}/", app_id, ep_id),
+            &format!("api/v1/app/{app_id}/endpoint/{ep_id}/"),
             ep,
             StatusCode::OK,
         )
@@ -176,7 +176,7 @@ pub async fn common_test_list<
 
     // Limit results
     let list = client
-        .get::<ListResponse<ModelOut>>(&format!("{}?limit=1", path), StatusCode::OK)
+        .get::<ListResponse<ModelOut>>(&format!("{path}?limit=1"), StatusCode::OK)
         .await
         .unwrap();
 
@@ -184,7 +184,7 @@ pub async fn common_test_list<
     assert!(!list.done);
 
     let list = client
-        .get::<ListResponse<ModelOut>>(&format!("{}?limit=50", path), StatusCode::OK)
+        .get::<ListResponse<ModelOut>>(&format!("{path}?limit=50"), StatusCode::OK)
         .await
         .unwrap();
 
@@ -192,7 +192,7 @@ pub async fn common_test_list<
     assert!(list.done);
 
     let list = client
-        .get::<ListResponse<ModelOut>>(&format!("{}?limit=10", path), StatusCode::OK)
+        .get::<ListResponse<ModelOut>>(&format!("{path}?limit=10"), StatusCode::OK)
         .await
         .unwrap();
 
@@ -200,7 +200,7 @@ pub async fn common_test_list<
     assert!(list.done);
 
     let list = client
-        .get::<ListResponse<ModelOut>>(&format!("{}?limit=6", path), StatusCode::OK)
+        .get::<ListResponse<ModelOut>>(&format!("{path}?limit=6"), StatusCode::OK)
         .await
         .unwrap();
 
@@ -220,7 +220,7 @@ pub async fn common_test_list<
 
     let _list = client
         .get::<IgnoredResponse>(
-            &format!("{}?limit=6&iterator=BAD-$$$ITERATOR", path),
+            &format!("{path}?limit=6&iterator=BAD-$$$ITERATOR"),
             StatusCode::UNPROCESSABLE_ENTITY,
         )
         .await
@@ -239,13 +239,13 @@ pub async fn common_test_list<
     // to 250
     if client
         .get::<IgnoredResponse>(
-            &format!("{}?limit=300", path),
+            &format!("{path}?limit=300"),
             StatusCode::UNPROCESSABLE_ENTITY,
         )
         .await
         .is_ok()
         || client
-            .get::<ListResponse<ModelOut>>(&format!("{}?limit=300", path), StatusCode::OK)
+            .get::<ListResponse<ModelOut>>(&format!("{path}?limit=300"), StatusCode::OK)
             .await
             .unwrap()
             .data
@@ -274,7 +274,7 @@ pub async fn get_msg_attempt_list_and_assert_count(
     run_with_retries(|| async {
         let list: ListResponse<MessageAttemptOut> = client
             .get(
-                &format!("api/v1/app/{}/attempt/msg/{}/", app_id, msg_id),
+                &format!("api/v1/app/{app_id}/attempt/msg/{msg_id}/"),
                 StatusCode::OK,
             )
             .await?;
@@ -290,3 +290,7 @@ pub async fn get_msg_attempt_list_and_assert_count(
     })
     .await
 }
+
+pub fn metadata(s: &str) -> Metadata {
+    serde_json::from_str::<Metadata>(s).unwrap()
+}
diff --git a/server/svix-server/tests/utils/mod.rs b/server/svix-server/tests/utils/mod.rs
index a08800188..94ea32adb 100644
--- a/server/svix-server/tests/utils/mod.rs
+++ b/server/svix-server/tests/utils/mod.rs
@@ -19,6 +19,7 @@ use svix_server::{
         security::generate_org_token,
         types::{BaseId, OrganizationId},
     },
+    setup_tracing,
 };
 
 use http::HeaderMap;
@@ -34,7 +35,7 @@ pub struct TestClient {
 
 impl TestClient {
     pub fn set_auth_header(&mut self, auth_header: String) {
-        self.auth_header = format!("Bearer {}", auth_header);
+        self.auth_header = format!("Bearer {auth_header}");
     }
 }
 
@@ -54,7 +55,7 @@ impl TestClient {
     pub fn new(base_uri: String, auth_token: &str) -> TestClient {
         TestClient {
             base_uri,
-            auth_header: format!("Bearer {}", auth_token),
+            auth_header: format!("Bearer {auth_token}"),
             client: Client::new(),
         }
     }
@@ -115,7 +116,7 @@ impl TestClient {
                     .context("error receiving/parsing response")
             }
             Err(e) => {
-                println!("Unexpected request error: {:?}", e);
+                println!("Unexpected request error: {e:?}");
                 Err(e.into())
             }
         }
@@ -240,6 +241,8 @@ pub async fn start_svix_server_with_cfg_and_org_id(
     cfg: &ConfigurationInner,
     org_id: OrganizationId,
 ) -> (TestClient, tokio::task::JoinHandle<()>) {
+    setup_tracing(cfg);
+
     let cfg = Arc::new(cfg.clone());
 
     let token = generate_org_token(&cfg.jwt_secret, org_id).unwrap();
@@ -313,13 +316,13 @@ impl TestReceiver {
 }
 
 async fn test_receiver_route(
-    axum::Json(json): axum::Json<serde_json::Value>,
     axum::extract::Extension(ref tx): axum::extract::Extension<mpsc::Sender<serde_json::Value>>,
     axum::extract::Extension(ref header_tx): axum::extract::Extension<mpsc::Sender<HeaderMap>>,
     axum::extract::Extension(response_status_code): axum::extract::Extension<
         Arc<Mutex<ResponseStatusCode>>,
     >,
     headers: HeaderMap,
+    axum::Json(json): axum::Json<serde_json::Value>,
 ) -> axum::http::StatusCode {
     tx.send(json).await.unwrap();
     header_tx.send(headers).await.unwrap();
@@ -336,7 +339,7 @@ where
         if out.is_ok() {
             return out;
         } else if let Err(err) = out {
-            println!("Attempt {}: {}", attempt, err);
+            println!("Attempt {attempt}: {err}");
         }
 
         tokio::time::sleep(std::time::Duration::from_millis(50)).await;
diff --git a/server/svix-server/tests/worker.rs b/server/svix-server/tests/worker.rs
index acfa6cc37..8e3f5cc96 100644
--- a/server/svix-server/tests/worker.rs
+++ b/server/svix-server/tests/worker.rs
@@ -95,7 +95,7 @@ async fn test_no_redirects_policy() {
     run_with_retries(|| async {
         let attempts: ListResponse<MessageAttemptOut> = client
             .get(
-                &format!("api/v1/app/{}/attempt/msg/{}/", app_id, msg_id),
+                &format!("api/v1/app/{app_id}/attempt/msg/{msg_id}/"),
                 StatusCode::OK,
             )
             .await
@@ -154,7 +154,7 @@ async fn test_endpoint_disable_on_repeated_failure() {
         run_with_retries(|| async {
             let ep: EndpointOut = client
                 .get(
-                    &format!("api/v1/app/{}/endpoint/{}/", app_id, ep_id),
+                    &format!("api/v1/app/{app_id}/endpoint/{ep_id}/"),
                     StatusCode::OK,
                 )
                 .await
@@ -206,7 +206,7 @@ async fn test_endpoint_disable_expiration_duration() {
         tokio::time::sleep(Duration::from_millis(500)).await;
         let ep: EndpointOut = client
             .get(
-                &format!("api/v1/app/{}/endpoint/{}/", app_id, ep_id),
+                &format!("api/v1/app/{app_id}/endpoint/{ep_id}/"),
                 StatusCode::OK,
             )
             .await
@@ -317,7 +317,7 @@ async fn test_endpoint_disable_on_sporadic_failure() {
         tokio::time::sleep(Duration::from_millis(500)).await;
         let ep: EndpointOut = client
             .get(
-                &format!("api/v1/app/{}/endpoint/{}/", app_id, ep_id),
+                &format!("api/v1/app/{app_id}/endpoint/{ep_id}/"),
                 StatusCode::OK,
             )
             .await
diff --git a/server/testing-docker-compose.yml b/server/testing-docker-compose.yml
index 825f95ef1..a3151d8c0 100644
--- a/server/testing-docker-compose.yml
+++ b/server/testing-docker-compose.yml
@@ -22,7 +22,6 @@ services:
       REDIS_NODES: "redis-cluster redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4"
       REDIS_CLUSTER_CREATOR: "yes"
       REDIS_CLUSTER_REPLICAS: "1"
-      REDIS_CLUSTER_ANNOUNCE_PORT: "6380"
     ports:
       - "6380:6379"
     depends_on:
@@ -39,7 +38,6 @@ services:
     environment:
       ALLOW_EMPTY_PASSWORD: "yes"
       REDIS_NODES: "redis-cluster redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4"
-      REDIS_CLUSTER_ANNOUNCE_PORT: "6381"
 
   redis-cluster-node-1:
     image: "bitnami/redis-cluster:6.2"
@@ -48,7 +46,6 @@ services:
     environment:
       ALLOW_EMPTY_PASSWORD: "yes"
       REDIS_NODES: "redis-cluster redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4"
-      REDIS_CLUSTER_ANNOUNCE_PORT: "6382"
 
   redis-cluster-node-2:
     image: "bitnami/redis-cluster:6.2"
@@ -57,7 +54,6 @@ services:
     environment:
       ALLOW_EMPTY_PASSWORD: "yes"
       REDIS_NODES: "redis-cluster redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4"
-      REDIS_CLUSTER_ANNOUNCE_PORT: "6383"
 
   redis-cluster-node-3:
     image: "bitnami/redis-cluster:6.2"
@@ -66,7 +62,6 @@ services:
     environment:
       ALLOW_EMPTY_PASSWORD: "yes"
       REDIS_NODES: "redis-cluster redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4"
-      REDIS_CLUSTER_ANNOUNCE_PORT: "6384"
 
   redis-cluster-node-4:
     image: "bitnami/redis-cluster:6.2"
@@ -75,7 +70,6 @@ services:
     environment:
       ALLOW_EMPTY_PASSWORD: "yes"
       REDIS_NODES: "redis-cluster redis-cluster-node-0 redis-cluster-node-1 redis-cluster-node-2 redis-cluster-node-3 redis-cluster-node-4"
-      REDIS_CLUSTER_ANNOUNCE_PORT: "6385"
 
 volumes:
   postgres-data: