feat: add tests for webhook resolvers

This commit is contained in:
Lakhan Samani 2022-07-11 19:40:54 +05:30
parent 334041d0e4
commit 018a13ab3c
24 changed files with 381 additions and 31 deletions

View File

@ -145,5 +145,17 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
if err != nil {
return err
}
query := fmt.Sprintf("FOR d in %s FILTER d.event_id == @event_id REMOVE { _key: d._key }", models.Collections.WebhookLog)
bindVars := map[string]interface{}{
"event_id": webhook.ID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return err
}
defer cursor.Close()
return nil
}

View File

@ -142,5 +142,11 @@ func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string)
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, webhook.ID)
err := p.db.Query(query).Exec()
if err != nil {
return err
}
query = fmt.Sprintf("DELETE FROM %s WHERE webhook_id = '%s'", KeySpace+"."+models.Collections.WebhookLog, webhook.ID)
err = p.db.Query(query).Exec()
return err
}

View File

@ -110,5 +110,11 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
return err
}
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
_, err = webhookLogCollection.DeleteOne(nil, bson.M{"webhook_id": webhook.ID}, options.Delete())
if err != nil {
return err
}
return nil
}

View File

@ -44,5 +44,6 @@ func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string)
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
// Also delete webhook logs for given webhook id
return nil
}

View File

@ -69,7 +69,7 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
result := p.db.Where("id = ?", webhookID).First(webhook)
result := p.db.Where("id = ?", webhookID).First(&webhook)
if result.Error != nil {
return nil, result.Error
}
@ -80,7 +80,7 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
result := p.db.Where("event_name = ?", eventName).First(webhook)
result := p.db.Where("event_name = ?", eventName).First(&webhook)
if result.Error != nil {
return nil, result.Error
}
@ -89,7 +89,14 @@ func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string)
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
result := p.db.Delete(&models.Webhook{}, webhook.ID)
result := p.db.Delete(&models.Webhook{
ID: webhook.ID,
})
if result.Error != nil {
return result.Error
}
result = p.db.Where("webhook_id = ?", webhook.ID).Delete(&models.WebhookLog{})
if result.Error != nil {
return result.Error
}

View File

@ -2026,7 +2026,7 @@ type TestEndpointResponse {
}
input ListWebhookLogRequest {
pagination: PaginatedInput!
pagination: PaginationInput!
webhook_id: String
}
@ -9675,7 +9675,7 @@ func (ec *executionContext) unmarshalInputListWebhookLogRequest(ctx context.Cont
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("pagination"))
it.Pagination, err = ec.unmarshalNPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx, v)
it.Pagination, err = ec.unmarshalNPaginationInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginationInput(ctx, v)
if err != nil {
return it, err
}
@ -12363,11 +12363,6 @@ func (ec *executionContext) unmarshalNOAuthRevokeInput2githubᚗcomᚋauthorizer
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) {
res, err := ec.unmarshalInputPaginatedInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNPagination2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPagination(ctx context.Context, sel ast.SelectionSet, v *model.Pagination) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
@ -12378,6 +12373,11 @@ func (ec *executionContext) marshalNPagination2ᚖgithubᚗcomᚋauthorizerdev
return ec._Pagination(ctx, sel, v)
}
func (ec *executionContext) unmarshalNPaginationInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginationInput(ctx context.Context, v interface{}) (*model.PaginationInput, error) {
res, err := ec.unmarshalInputPaginationInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNResendVerifyEmailInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResendVerifyEmailInput(ctx context.Context, v interface{}) (model.ResendVerifyEmailInput, error) {
res, err := ec.unmarshalInputResendVerifyEmailInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)

View File

@ -108,7 +108,7 @@ type InviteMemberInput struct {
}
type ListWebhookLogRequest struct {
Pagination *PaginatedInput `json:"pagination"`
Pagination *PaginationInput `json:"pagination"`
WebhookID *string `json:"webhook_id"`
}

View File

@ -355,7 +355,7 @@ type TestEndpointResponse {
}
input ListWebhookLogRequest {
pagination: PaginatedInput!
pagination: PaginationInput!
webhook_id: String
}

View File

@ -219,7 +219,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
go func() {
if isSignUp {
utils.RegisterEvent(ctx, constants.EnvKeyDisableSignUp, provider, user)
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, provider, user)
} else {
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, provider, user)
}

View File

@ -43,5 +43,7 @@ func DeleteWebhookResolver(ctx context.Context, params model.WebhookRequest) (*m
return nil, err
}
panic(fmt.Errorf("not implemented"))
return &model.Response{
Message: "Webhook deleted successfully",
}, nil
}

View File

@ -63,7 +63,7 @@ func TestEndpointResolver(ctx context.Context, params model.TestEndpointRequest)
}
if params.EventName == constants.UserLoginWebhookEvent {
reqBody["login_method"] = constants.AuthRecipeMethodMagicLinkLogin
reqBody["auth_recipe"] = constants.AuthRecipeMethodMagicLinkLogin
}
requestBody, err := json.Marshal(reqBody)

View File

@ -52,7 +52,7 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
CreatedAt: *webhook.CreatedAt,
}
if webhookDetails.EventName != utils.StringValue(params.EventName) {
if params.EventName != nil && webhookDetails.EventName != utils.StringValue(params.EventName) {
if isValid := validators.IsValidWebhookEventName(utils.StringValue(params.EventName)); !isValid {
log.Debug("invalid event name: ", utils.StringValue(params.EventName))
return nil, fmt.Errorf("invalid event name %s", utils.StringValue(params.EventName))
@ -60,11 +60,11 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
webhookDetails.EventName = utils.StringValue(params.EventName)
}
if webhookDetails.EndPoint != utils.StringValue(params.Endpoint) {
if params.Endpoint != nil && webhookDetails.EndPoint != utils.StringValue(params.Endpoint) {
webhookDetails.EventName = utils.StringValue(params.EventName)
}
if webhookDetails.Enabled != utils.BoolValue(params.Enabled) {
if params.Enabled != nil && webhookDetails.Enabled != utils.BoolValue(params.Enabled) {
webhookDetails.Enabled = utils.BoolValue(params.Enabled)
}

View File

@ -24,7 +24,9 @@ func WebhookLogsResolver(ctx context.Context, params model.ListWebhookLogRequest
return nil, fmt.Errorf("unauthorized")
}
pagination := utils.GetPagination(params.Pagination)
pagination := utils.GetPagination(&model.PaginatedInput{
Pagination: params.Pagination,
})
webhookLogs, err := db.Provider.ListWebhookLogs(ctx, pagination, utils.StringValue(params.WebhookID))
if err != nil {

View File

@ -1 +1,39 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func addWebhookTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run("should add webhook", func(t *testing.T) {
req, ctx := createContext(s)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.NoError(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
for _, eventType := range s.TestInfo.TestEventTypes {
webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{
EventName: eventType,
Endpoint: s.TestInfo.WebhookEndpoint,
Enabled: true,
Headers: map[string]interface{}{
"x-test": "foo",
},
})
assert.NoError(t, err)
assert.NotNil(t, webhook)
assert.NotEmpty(t, webhook.Message)
}
})
}

View File

@ -1 +1,50 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func deleteWebhookTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run("should delete webhook", func(t *testing.T) {
req, ctx := createContext(s)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.NoError(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
// get all webhooks
webhooks, err := db.Provider.ListWebhook(ctx, model.Pagination{})
assert.NoError(t, err)
for _, w := range webhooks.Webhooks {
res, err := resolvers.DeleteWebhookResolver(ctx, model.WebhookRequest{
ID: w.ID,
})
assert.NoError(t, err)
assert.NotNil(t, res)
assert.NotEmpty(t, res.Message)
}
webhooks, err = db.Provider.ListWebhook(ctx, model.Pagination{})
assert.NoError(t, err)
assert.Len(t, webhooks.Webhooks, 0)
webhookLogs, err := db.Provider.ListWebhookLogs(ctx, model.Pagination{
Limit: 10,
}, "")
assert.NoError(t, err)
assert.Len(t, webhookLogs.WebhookLogs, 0)
})
}

View File

@ -3,6 +3,7 @@ package test
import (
"context"
"testing"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
@ -41,9 +42,14 @@ func TestResolvers(t *testing.T) {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEnv, "test")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyIsProd, false)
t.Run("should pass tests for "+dbType, func(t *testing.T) {
// admin tests
// admin resolvers tests
adminSignupTests(t, s)
addWebhookTest(t, s) // add webhooks for all the system events
testEndpointTest(t, s)
verificationRequestsTest(t, s)
updateWebhookTest(t, s)
webhookTest(t, s)
webhooksTest(t, s)
usersTest(t, s)
deleteUserTest(t, s)
updateUserTest(t, s)
@ -56,7 +62,7 @@ func TestResolvers(t *testing.T) {
enableAccessTest(t, s)
generateJWTkeyTest(t, s)
// user tests
// user resolvers tests
loginTests(t, s)
signupTests(t, s)
forgotPasswordTest(t, s)
@ -71,6 +77,10 @@ func TestResolvers(t *testing.T) {
metaTests(t, s)
inviteUserTest(t, s)
validateJwtTokenTest(t, s)
time.Sleep(5 * time.Second) // add sleep for webhooklogs to get generated as they are async
webhookLogsTest(t, s) // get logs after above resolver tests are done
deleteWebhookTest(t, s) // delete webhooks (admin resolver)
})
}
}

View File

@ -23,6 +23,8 @@ import (
type TestData struct {
Email string
Password string
WebhookEndpoint string
TestEventTypes []string
}
type TestSetup struct {
@ -77,6 +79,8 @@ func testSetup() TestSetup {
testData := TestData{
Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()),
Password: "Test@123",
WebhookEndpoint: "https://62cbc6738042b16aa7c22df2.mockapi.io/api/v1/webhook",
TestEventTypes: []string{constants.UserAccessEnabledWebhookEvent, constants.UserAccessRevokedWebhookEvent, constants.UserCreatedWebhookEvent, constants.UserDeletedWebhookEvent, constants.UserLoginWebhookEvent, constants.UserSignUpWebhookEvent},
}
err := os.Setenv(constants.EnvKeyEnvPath, "../../.env.test")

View File

@ -1 +1,37 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func testEndpointTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run("should test endpoint", func(t *testing.T) {
req, ctx := createContext(s)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.NoError(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
res, err := resolvers.TestEndpointResolver(ctx, model.TestEndpointRequest{
Endpoint: s.TestInfo.WebhookEndpoint,
EventName: constants.UserLoginWebhookEvent,
Headers: map[string]interface{}{
"x-test": "test",
},
})
assert.NoError(t, err)
assert.NotNil(t, res)
assert.GreaterOrEqual(t, int64(201), *res.HTTPStatus)
assert.NotEmpty(t, res.Response)
})
}

View File

@ -1 +1,60 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func updateWebhookTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run("should update webhook", func(t *testing.T) {
req, ctx := createContext(s)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.NoError(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
// get webhook
webhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
assert.NoError(t, err)
assert.NotNil(t, webhook)
webhook.Headers["x-new-test"] = "new-test"
res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: webhook.ID,
Headers: webhook.Headers,
Enabled: utils.NewBoolRef(false),
})
assert.NoError(t, err)
assert.NotEmpty(t, res)
assert.NotEmpty(t, res.Message)
updatedWebhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
assert.NoError(t, err)
assert.NotNil(t, updatedWebhook)
assert.Equal(t, webhook.ID, updatedWebhook.ID)
assert.Equal(t, utils.StringValue(webhook.EventName), utils.StringValue(updatedWebhook.EventName))
assert.Equal(t, utils.StringValue(webhook.Endpoint), utils.StringValue(updatedWebhook.Endpoint))
assert.Len(t, updatedWebhook.Headers, 2)
assert.False(t, utils.BoolValue(updatedWebhook.Enabled))
res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: webhook.ID,
Headers: webhook.Headers,
Enabled: utils.NewBoolRef(true),
})
assert.NoError(t, err)
assert.NotEmpty(t, res)
assert.NotEmpty(t, res.Message)
})
}

View File

@ -1 +1,45 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func webhookLogsTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run("should get webhook logs", func(t *testing.T) {
req, ctx := createContext(s)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.NoError(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
webhooks, err := resolvers.WebhooksResolver(ctx, nil)
assert.NoError(t, err)
assert.NotEmpty(t, webhooks)
webhookLogs, err := resolvers.WebhookLogsResolver(ctx, model.ListWebhookLogRequest{})
assert.NoError(t, err)
assert.Greater(t, len(webhookLogs.WebhookLogs), 1)
for _, w := range webhooks.Webhooks {
webhookLogs, err := resolvers.WebhookLogsResolver(ctx, model.ListWebhookLogRequest{
WebhookID: &w.ID,
})
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(webhookLogs.WebhookLogs), 1)
for _, wl := range webhookLogs.WebhookLogs {
assert.Equal(t, utils.StringValue(wl.WebhookID), w.ID)
}
}
})
}

View File

@ -0,0 +1,42 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func webhookTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run("should get webhook", func(t *testing.T) {
req, ctx := createContext(s)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.NoError(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
// get webhook by event name
webhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent)
assert.NoError(t, err)
assert.NotNil(t, webhook)
res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{
ID: webhook.ID,
})
assert.NoError(t, err)
assert.Equal(t, res.ID, webhook.ID)
assert.Equal(t, utils.StringValue(res.Endpoint), utils.StringValue(webhook.Endpoint))
assert.Equal(t, utils.StringValue(res.EventName), utils.StringValue(webhook.EventName))
assert.Equal(t, utils.BoolValue(res.Enabled), utils.BoolValue(webhook.Enabled))
assert.Len(t, res.Headers, len(webhook.Headers))
})
}

View File

@ -1 +1,29 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func webhooksTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run("should get webhooks", func(t *testing.T) {
req, ctx := createContext(s)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.NoError(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
webhooks, err := resolvers.WebhooksResolver(ctx, nil)
assert.NoError(t, err)
assert.NotEmpty(t, webhooks)
assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestEventTypes))
})
}

View File

@ -23,7 +23,7 @@ func NewBoolRef(v bool) *bool {
// BoolValue returns the value of the given bool ref
func BoolValue(r *bool) bool {
if r != nil {
if r == nil {
return false
}
return *r

View File

@ -20,7 +20,11 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
return err
}
userBytes, err := json.Marshal(user)
if !BoolValue(webhook.Enabled) {
return nil
}
userBytes, err := json.Marshal(user.AsAPIUser())
if err != nil {
log.Debug("error marshalling user obj: ", err)
return err
@ -78,7 +82,7 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
statusCode := int64(resp.StatusCode)
_, err = db.Provider.AddWebhookLog(ctx, models.WebhookLog{
HttpStatus: statusCode,
Request: string(requestBytesBuffer.Bytes()),
Request: string(requestBody),
Response: string(responseBytes),
WebhookID: webhook.ID,
})