forked from EasyPost/easypost-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
webhook.go
174 lines (147 loc) · 7.01 KB
/
webhook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package easypost
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"time"
"golang.org/x/text/unicode/norm"
)
// A Webhook represents an EasyPost webhook callback URL.
type Webhook struct {
ID string `json:"id,omitempty"`
Object string `json:"object,omitempty"`
Mode string `json:"mode,omitempty"`
URL string `json:"url,omitempty"`
DisabledAt *time.Time `json:"disabled_at,omitempty"`
WebhookSecret string `json:"webhook_secret,omitempty"`
}
// CreateUpdateWebhookOptions is used to specify parameters for creating and updating EasyPost webhooks.
type CreateUpdateWebhookOptions struct {
URL string `json:"url,omitempty"`
WebhookSecret string `json:"webhook_secret,omitempty"`
}
// createUpdateWebhookRequest is the request struct for creating and updating webhooks (internal)
type createUpdateWebhookRequest struct {
Webhook *Webhook `json:"webhook,omitempty"`
}
// listWebhooksResult is the response struct of listing webhooks (internal)
type listWebhooksResult struct {
Webhooks *[]*Webhook `json:"webhooks,omitempty"`
}
func (c *Client) composeCreateUpdateWebhookRequest(data *CreateUpdateWebhookOptions) *createUpdateWebhookRequest {
return &createUpdateWebhookRequest{
Webhook: &Webhook{
URL: data.URL,
WebhookSecret: data.WebhookSecret,
},
}
}
// CreateWebhook creates a new webhook with the given URL.
// Deprecated: Use CreateWebhookWithDetails instead.
func (c *Client) CreateWebhook(u string) (out *Webhook, err error) {
return c.CreateWebhookWithContext(context.Background(), u)
}
// CreateWebhookWithContext performs the same operation as CreateWebhook, but
// allows specifying a context that can interrupt the request.
// Deprecated: Use CreateWebhookWithDetailsWithContext instead.
func (c *Client) CreateWebhookWithContext(ctx context.Context, u string) (out *Webhook, err error) {
return c.CreateWebhookWithDetailsWithContext(ctx, &CreateUpdateWebhookOptions{URL: u})
}
// CreateWebhookWithDetails creates a new webhook with the provided details.
func (c *Client) CreateWebhookWithDetails(data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
return c.CreateWebhookWithDetailsWithContext(context.Background(), data)
}
// CreateWebhookWithDetailsWithContext performs the same operation as CreateWebhookWithDetails, but
// allows specifying a context that can interrupt the request.
func (c *Client) CreateWebhookWithDetailsWithContext(ctx context.Context,
data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
req := c.composeCreateUpdateWebhookRequest(data)
err = c.post(ctx, "webhooks", req, &out)
return
}
// ListWebhooks returns all webhooks associated with the EasyPost account.
func (c *Client) ListWebhooks() (out []*Webhook, err error) {
return c.ListWebhooksWithContext(context.Background())
}
// ListWebhooksWithContext performs the same operation as
// ListWebhooksWithContext, but allows specifying a context that can interrupt
// the request.
func (c *Client) ListWebhooksWithContext(ctx context.Context) (out []*Webhook, err error) {
err = c.get(ctx, "webhooks", &listWebhooksResult{Webhooks: &out})
return
}
// GetWebhook retrieves a Webhook object with the given ID.
func (c *Client) GetWebhook(webhookID string) (out *Webhook, err error) {
return c.GetWebhookWithContext(context.Background(), webhookID)
}
// GetWebhookWithContext performs the same operation as GetWebhook, but allows
// specifying a context that can interrupt the request.
func (c *Client) GetWebhookWithContext(ctx context.Context, webhookID string) (out *Webhook, err error) {
err = c.get(ctx, "webhooks/"+webhookID, &out)
return
}
// Deprecated: Use UpdateWebhook instead.
// EnableWebhook re-enables a disabled webhook.
func (c *Client) EnableWebhook(webhookID string) (out *Webhook, err error) {
return c.EnableWebhookWithContext(context.Background(), webhookID)
}
// Deprecated: Use UpdateWebhookWithContext instead.
// EnableWebhookWithContext performs the same operation as EnableWebhook, but
// allows specifying a context that can interrupt the request.
func (c *Client) EnableWebhookWithContext(ctx context.Context, webhookID string) (out *Webhook, err error) {
return c.UpdateWebhookWithContext(ctx, webhookID, &CreateUpdateWebhookOptions{})
}
// UpdateWebhook updates a webhook. Automatically re-enables webhook if it is disabled.
func (c *Client) UpdateWebhook(webhookID string, data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
return c.UpdateWebhookWithContext(context.Background(), webhookID, data)
}
// UpdateWebhookWithContext performs the same operation as UpdateWebhook, but
// allows specifying a context that can interrupt the request.
func (c *Client) UpdateWebhookWithContext(ctx context.Context,
webhookID string, data *CreateUpdateWebhookOptions) (out *Webhook, err error) {
req := c.composeCreateUpdateWebhookRequest(data)
err = c.patch(ctx, "webhooks/"+webhookID, req, &out)
return
}
// DeleteWebhook removes a webhook.
func (c *Client) DeleteWebhook(webhookID string) error {
return c.DeleteWebhookWithContext(context.Background(), webhookID)
}
// DeleteWebhookWithContext performs the same operation as DeleteWebhook, but
// allows specifying a context that can interrupt the request.
func (c *Client) DeleteWebhookWithContext(ctx context.Context, webhookID string) error {
return c.del(ctx, "webhooks/"+webhookID)
}
// ValidateWebhook validates a webhook by comparing the HMAC signature header sent from EasyPost to your shared secret.
// If the signatures do not match, an error will be raised signifying the webhook either did not originate
// from EasyPost or the secrets do not match. If the signatures do match, the `event_body` will be returned as JSON.
func (c *Client) ValidateWebhook(eventBody []byte, headers map[string]interface{}, webhookSecret string) (out *Event, err error) {
return c.ValidateWebhookWithContext(context.Background(), eventBody, headers, webhookSecret)
}
// ValidateWebhookWithContext performs the same operation as ValidateWebhook, but
// allows specifying a context that can interrupt the request.
func (c *Client) ValidateWebhookWithContext(ctx context.Context, eventBody []byte, headers map[string]interface{}, webhookSecret string) (out *Event, err error) {
easypostHmacSignature, signaturePresent := headers["X-Hmac-Signature"].(string)
if signaturePresent {
normalizedSecret := norm.NFKD.String(webhookSecret)
encodedSecret := []byte(normalizedSecret)
expectedSignature := hmac.New(sha256.New, encodedSecret)
expectedSignature.Write(eventBody)
digest := "hmac-sha256-hex=" + hex.EncodeToString(expectedSignature.Sum(nil))
if hmac.Equal([]byte(digest), []byte(easypostHmacSignature)) {
var webhookBody Event
if err = json.Unmarshal(eventBody, &webhookBody); err != nil {
return nil, err
} else {
return &webhookBody, nil
}
} else {
return nil, errors.New("weebhook received did not originate from EasyPost or had a webhook secret mismatch")
}
} else {
return nil, errors.New("webhook received does not contain an HMAC signature")
}
}