# Webhook

Each `Webhook` contains the `url` that EasyPost notifies when an object in the system updates.
Several types of objects are processed asynchronously within EasyPost. When an object updates, an `Event` is sent via `HTTP POST` to each configured
`webhook url`. The `webhook` object supports create, retrieve, update, and delete (CRUD) operations.

For best practices and request sanitation, refer to the Webhooks Guide.

Webhook endpoints must return a `2XX status code` to indicate successful receipt. A `200 OK` response is preferred. Endpoints that repeatedly return failures may be
automatically disabled; disabled webhooks can be re-enabled using the `Webhook` update endpoint.

---

## Webhook Authentication

Securing webhook endpoints is crucial to ensure data integrity and authenticity. EasyPost recommends two primary methods for securing webhooks:

### HMAC Validation

HMAC validation ensures webhook data is untampered during transmission.

- Include a `webhook_secret` when creating or updating a webhook.
- EasyPost generates a signature with this secret, sent via the `X-Hmac-Signature` header in each event.
- Validate the signature using `validate_webhook()` from EasyPost’s client libraries (function name may vary depending on the programming language; refer to the library documentation).
- If the signature does not match, reject the event to prevent fraudulent data.

### Basic Authentication

Basic authentication requires embedding a username and password in the webhook URL.

**Example**

`https://username:secret@example.com/easypost-webhook`

Each webhook delivery includes an `Authorization` header. Validate the credentials against the stored values.

---

## TLS and Certificate Validation

EasyPost performs certificate validation and requires that any webhook recipient using TLS (HTTPS) must have a certificate signed by a trusted public
certification authority. This helps ensure that the data being transmitted is secure.

- Webhook endpoints must use `HTTPS`.
- SSLv2, SSLv3, and export-grade ciphers are not supported.

For server setup guidance, refer to:

- Mozilla's Server-Side TLS Guide

- Qualys's SSL/TLS Deployment Best Practices guide.

---

## Webhook object

| Property | Type | Description |
|----------|------|-------------|
| id | string | Unique, begins with "hook_" |
| object | string | "Webhook" |
| url | string | http://example.com |
| disabled_at | datetime | The timestamp at which the Webhook was most recently disabled (if any) |
| custom_headers | []map[string]string | A list of key-value pairs, each containing "name" and "value" fields. If not specified during creation, this field defaults to an empty array []. |

Example Object:

```json
{
  "id": "hook_d31edbc62d1511f0a15761d2f710980c",
  "object": "Webhook",
  "mode": "test",
  "url": "http://example.com",
  "created_at": "2025-05-09T20:40:21Z",
  "disabled_at": null,
  "custom_headers": [
    {
      "name": "X-Header-Name",
      "value": "header_value"
    }
  ]
}
```

---

## Custom Outbound Headers

EasyPost supports optional custom headers on outbound webhook requests, allowing up to **three custom headers**. Each header must include a `name` and
`value`, both as ASCII strings under 1000 characters.

### Validation Requirements

- Custom headers must be an **array of key-value pairs**.
- Each `name` must be unique within the list.
- If specified in a `POST` or `PATCH` request, both `name` and `value` are mandatory.
- Headers prefixed with `X-EasyPost-*` **cannot be used**.
- Existing `X-*` standard name or non-standard `X-*` headers may be overwritten if a conflict exists.
- Header values are stored in plaintext. Use encrypted values if sensitive information is included.

### Updating Custom Headers

Updating replaces the entire `custom_headers` list. Partial updates are **not** supported.

### RFC Compliance

All custom header names must conform to RFC 7230/ RFC 9112 formatting rules.

---

## Create a Webhook

### Example: POST /webhooks

#### cURL

```shell
curl -X POST https://api.easypost.com/v2/webhooks \
  -u "EASYPOST_API_KEY": \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook": {
      "url": "example.com",
      "webhook_secret": "A1B2C3",
      "custom_headers": [{
        "name": "X-Header-Name",
        "value": "header_value"
      }],
    }
  }'
```

#### Go

```go
package example

import (
	"fmt"

	"github.com/EasyPost/easypost-go/v5"
)

func create() {
	client := easypost.New("EASYPOST_API_KEY")

	webhook, _ := client.CreateWebhook(
		&easypost.CreateUpdateWebhookOptions{
			URL:           "example.com",
			WebhookSecret: "A1B2C3",
			CustomHeaders: []easypost.WebhookCustomHeader{
				{
					Name:  "X-Header-Name",
					Value: "header_value",
				},
			},
		},
	)

	fmt.Println(webhook)
}
```

#### Java

```java
package webhooks;

import com.easypost.exception.EasyPostException;
import com.easypost.model.Webhook;
import com.easypost.service.EasyPostClient;

import java.util.HashMap;

public class Create {
    public static void main(String[] args) throws EasyPostException {
        EasyPostClient client = new EasyPostClient("EASYPOST_API_KEY");

        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("url", "example.com");
        params.put("webhook_secret", "A1B2C3");
        ArrayList<HashMap<String, String>> customHeaders = new ArrayList<>();
        HashMap<String, String> header = new HashMap<>();
        header.put("name", "X-Header-Name");
        header.put("value", "header_value");
        customHeaders.add(header);
        params.put("custom_headers", customHeaders);

        Webhook webhook = client.webhook.create(params);

        System.out.println(webhook);
    }
}
```

#### C#

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyPost.Models.API;
using Newtonsoft.Json;

namespace EasyPostExamples
{
    public class Examples
    {
        public static async Task Main()
        {
            var client = new EasyPost.Client(new EasyPost.ClientConfiguration("EASYPOST_API_KEY"));

            EasyPost.Parameters.Webhook.Create parameters = new()
            {
                Url = "example.com",
                Secret = "ABC123",
                CustomHeaders = new List<EasyPost.Models.API.WebhookCustomHeader>
                {
                    new EasyPost.Models.API.WebhookCustomHeader { Name = "X-Header-Name", Value = "header_value" }
                }
            };

            EasyPost.Models.API.Webhook webhook = await client.Webhook.Create(parameters);

            Console.WriteLine(JsonConvert.SerializeObject(webhook, Formatting.Indented));
        }
    }
}
```

#### Node.js

```javascript
const EasyPostClient = require('@easypost/api');

const client = new EasyPostClient('EASYPOST_API_KEY');

(async () => {
  const webhook = await client.Webhook.create({
    url: 'example.com',
    webhook_secret: 'A1B2C3',
    customer_headers: [
      {
        name: 'X-Header-Name',
        value: 'header_value',
      },
    ],
  });

  console.log(webhook);
})();
```

#### PHP

```php
<?php

$client = new \EasyPost\EasyPostClient('EASYPOST_API_KEY');

$webhook = $client->webhook->create([
    'url' => 'example.com',
    'webhook_secret' => 'A1B2C3',
    'custom_headers' => [
        ['name' => 'X-Header-Name'],
        ['value' => 'header_value'],
    ],
]);

echo $webhook;
```

#### Python

```python
import easypost

client = easypost.EasyPostClient("EASYPOST_API_KEY")

webhook = client.webhook.create(
    url="example.com",
    webhook_secret="A1B2C3",
    custom_headers=[
        {
            "name": "X-Header-Name",
            "value": "header_value",
        }
    ],
)

print(webhook)
```

#### Ruby

```ruby
require 'easypost'

client = EasyPost::Client.new(api_key: 'EASYPOST_API_KEY')

webhook = client.webhook.create(
  url: 'example.com',
  webhook_secret: 'A1B2C3',
  custom_headers: [
    {
      name: 'X-Header-Name',
      value: 'header_value',
    },
  ],
)

puts webhook
```

To create a `Webhook`, provide a `url` parameter to designate where notifications are sent. A webhook can be secured using a `webhook_secret` for HMAC validation or basic authentication. The optional `custom_headers` attribute enables the inclusion of additional header information, such as authentication tokens or metadata, with outbound webhook requests.

---

## Retrieve all Webhooks

### Example: GET /webhooks

#### cURL

```shell
curl -X GET https://api.easypost.com/v2/webhooks \
  -u "EASYPOST_API_KEY":
```

#### Go

```go
package example

import (
	"fmt"

	"github.com/EasyPost/easypost-go/v5"
)

func list() {
	client := easypost.New("EASYPOST_API_KEY")

	webhooks, _ := client.ListWebhooks()

	fmt.Println(webhooks)
}
```

#### Java

```java
package webhooks;

import com.easypost.exception.EasyPostException;
import com.easypost.model.WebhookCollection;
import com.easypost.service.EasyPostClient;

public class All {
    public static void main(String[] args) throws EasyPostException {
        EasyPostClient client = new EasyPostClient("EASYPOST_API_KEY");

        WebhookCollection webhooks = client.webhook.all();

        System.out.println(webhooks);
    }
}
```

#### C#

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyPost;
using Newtonsoft.Json;

namespace EasyPostExamples
{
    public class Examples
    {
        public static async Task Main()
        {
            var client = new EasyPost.Client(new EasyPost.ClientConfiguration("EASYPOST_API_KEY"));

            EasyPost.Parameters.Webhook.All parameters = new()
            {
                PageSize = 5,
            };

            List<EasyPost.Models.API.Webhook> webhooks = await client.Webhook.All(parameters);

            Console.WriteLine(JsonConvert.SerializeObject(webhooks, Formatting.Indented));
        }
    }
}
```

#### Node.js

```javascript
const EasyPostClient = require('@easypost/api');

const client = new EasyPostClient('EASYPOST_API_KEY');

(async () => {
  const webhooks = await client.Webhook.all();

  console.log(webhooks);
})();
```

#### PHP

```php
<?php

$client = new \EasyPost\EasyPostClient('EASYPOST_API_KEY');

$webhooks = $client->webhook->all();

echo $webhooks;
```

#### Python

```python
import easypost

client = easypost.EasyPostClient("EASYPOST_API_KEY")

webhooks = client.webhook.all()

print(webhooks)
```

#### Ruby

```ruby
require 'easypost'

client = EasyPost::Client.new(api_key: 'EASYPOST_API_KEY')

webhooks = client.webhook.all

puts webhooks
```

Retrieve an unpaginated list of all `Webhooks` associated with the `API Key`.

---

## Retrieve a Webhook

### Example: GET /webhooks/:id

#### cURL

```shell
curl -X GET https://api.easypost.com/v2/webhooks/hook_... \
  -u "EASYPOST_API_KEY":
```

#### Go

```go
package example

import (
	"fmt"

	"github.com/EasyPost/easypost-go/v5"
)

func retrieve() {
	client := easypost.New("EASYPOST_API_KEY")

	webhook, _ := client.GetWebhook("hook_...")

	fmt.Println(webhook)
}
```

#### Java

```java
package webhooks;

import com.easypost.exception.EasyPostException;
import com.easypost.model.Webhook;
import com.easypost.service.EasyPostClient;

public class Retrieve {
    public static void main(String[] args) throws EasyPostException {
        EasyPostClient client = new EasyPostClient("EASYPOST_API_KEY");

        Webhook webhook = client.webhook.retrieve("hook_...");

        System.out.println(webhook);
    }
}
```

#### C#

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyPost;
using Newtonsoft.Json;

namespace EasyPostExamples
{
    public class Examples
    {
        public static async Task Main()
        {
            var client = new EasyPost.Client(new EasyPost.ClientConfiguration("EASYPOST_API_KEY"));

            EasyPost.Models.API.Webhook webhook = await client.Webhook.Retrieve("hook_...");

            Console.WriteLine(JsonConvert.SerializeObject(webhook, Formatting.Indented));
        }
    }
}
```

#### Node.js

```javascript
const EasyPostClient = require('@easypost/api');

const client = new EasyPostClient('EASYPOST_API_KEY');

(async () => {
  const webhook = await client.Webhook.retrieve('hook_...');

  console.log(webhook);
})();
```

#### PHP

```php
<?php

$client = new \EasyPost\EasyPostClient('EASYPOST_API_KEY');

$webhook = $client->webhook->retrieve('hook_...');

echo $webhook;
```

#### Python

```python
import easypost

client = easypost.EasyPostClient("EASYPOST_API_KEY")

webhook = client.webhook.retrieve("hook_...")

print(webhook)
```

#### Ruby

```ruby
require 'easypost'

client = EasyPost::Client.new(api_key: 'EASYPOST_API_KEY')

webhook = client.webhook.retrieve('hook_...')

puts webhook
```

Retrieve a `Webhook` by `id`.

---

## Update a Webhook

### Example: PATCH /webhooks/:id

#### cURL

```shell
curl -X PATCH https://api.easypost.com/v2/webhooks/hook_... \
  -u "EASYPOST_API_KEY": \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook_secret": "A1B2C3",
    "custom_headers": [{
      "name": "X-Header-Name",
      "value": "header_value"
    }],
  }'
```

#### Go

```go
package example

import (
	"fmt"

	"github.com/EasyPost/easypost-go/v5"
)

func update() {
	client := easypost.New("EASYPOST_API_KEY")

	webhook, _ := client.UpdateWebhook(
		"hook_...",
		&easypost.CreateUpdateWebhookOptions{
			WebhookSecret: "A1B2C3",
			CustomHeaders: []easypost.WebhookCustomHeader{
				{
					Name:  "X-Header-Name",
					Value: "header_value",
				},
			},
		},
	)

	fmt.Println(webhook)
}
```

#### Java

```java
package webhooks;

import com.easypost.exception.EasyPostException;
import com.easypost.model.Webhook;
import com.easypost.service.EasyPostClient;

import java.util.HashMap;

public class Update {
    public static void main(String[] args) throws EasyPostException {
        EasyPostClient client = new EasyPostClient("EASYPOST_API_KEY");

        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("webhook_secret", "A1B2C3");
        ArrayList<HashMap<String, String>> customHeaders = new ArrayList<>();
        HashMap<String, String> header = new HashMap<>();
        header.put("name", "X-Header-Name");
        header.put("value", "header_value");
        customHeaders.add(header);
        params.put("custom_headers", customHeaders);

        Webhook webhook = client.webhook.update("hook_...", params);

        System.out.println(webhook);
    }
}
```

#### C#

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyPost;
using Newtonsoft.Json;

namespace EasyPostExamples
{
    public class Examples
    {
        public static async Task Main()
        {
            var client = new EasyPost.Client(new EasyPost.ClientConfiguration("EASYPOST_API_KEY"));

            EasyPost.Parameters.Webhook.Create parameters = new()
            {
                Secret = "ABC123",
                CustomHeaders = new List<EasyPost.Models.API.WebhookCustomHeader>
                {
                    new EasyPost.Models.API.WebhookCustomHeader { Name = "X-Header-Name", Value = "header_value" }
                }
            };

            EasyPost.Models.API.Webhook webhook = await client.Webhook.Update("hook_...", parameters);

            Console.WriteLine(JsonConvert.SerializeObject(webhook, Formatting.Indented));
        }
    }
}
```

#### Node.js

```javascript
const EasyPostClient = require('@easypost/api');

const client = new EasyPostClient('EASYPOST_API_KEY');

(async () => {
  const webhook = await client.Webhook.update('hook_...', {
    webhook_secret: 'A1B2C3',
    customer_headers: [
      {
        name: 'X-Header-Name',
        value: 'header_value',
      },
    ],
  });

  console.log(webhook);
})();
```

#### PHP

```php
<?php

$client = new \EasyPost\EasyPostClient('EASYPOST_API_KEY');

$webhook = $client->webhook->update('hook_...', [
    'webhook_secret' => 'A1B2C3',
    'custom_headers' => [
        ['name' => 'X-Header-Name'],
        ['value' => 'header_value'],
    ],
]);

echo $webhook;
```

#### Python

```python
import easypost

client = easypost.EasyPostClient("EASYPOST_API_KEY")

updated_webhook = client.webhook.update(
    "hook_...",
    webhook_secret="A1B2C3",
    custom_headers=[
        {
            "name": "X-Header-Name",
            "value": "header_value",
        }
    ],
)

print(updated_webhook)
```

#### Ruby

```ruby
require 'easypost'

client = EasyPost::Client.new(api_key: 'EASYPOST_API_KEY')

webhook = client.webhook.update(
  'hook_...', {
    webhook_secret: 'A1B2C3',
    custom_headers: [
      {
        name: 'X-Header-Name',
        value: 'header_value',
      },
    ],
  },
)

puts webhook
```

Re-enable a disabled `Webhook` or update the `webhook_secret` and `custom_headers`.

---

## Delete a Webhook

### Example: DELETE /webhooks/:id

#### cURL

```shell
curl -X DELETE https://api.easypost.com/v2/webhooks/hook_... \
  -u "EASYPOST_API_KEY":
```

#### Go

```go
package example

import (
	"fmt"

	"github.com/EasyPost/easypost-go/v5"
)

func delete() {
	client := easypost.New("EASYPOST_API_KEY")

	err := client.DeleteWebhook("hook_...")

	fmt.Println(err)
}
```

#### Java

```java
package webhooks;

import com.easypost.exception.EasyPostException;
import com.easypost.service.EasyPostClient;

public class Delete {
    public static void main(String[] args) throws EasyPostException {
        EasyPostClient client = new EasyPostClient("EASYPOST_API_KEY");

        client.webhook.delete("hook_...");
    }
}
```

#### C#

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using EasyPost;
using Newtonsoft.Json;

namespace EasyPostExamples
{
    public class Examples
    {
        public static async Task Main()
        {
            var client = new EasyPost.Client(new EasyPost.ClientConfiguration("EASYPOST_API_KEY"));

            await client.Webhook.Delete("hook_...");
        }
    }
}
```

#### Node.js

```javascript
const EasyPostClient = require('@easypost/api');

const client = new EasyPostClient('EASYPOST_API_KEY');

(async () => {
  await client.Webhook.delete('hook_...');
})();
```

#### PHP

```php
<?php

$client = new \EasyPost\EasyPostClient('EASYPOST_API_KEY');

$client->webhook->delete('hook_...');
```

#### Python

```python
import easypost

client = easypost.EasyPostClient("EASYPOST_API_KEY")

client.webhook.delete("hook_...")
```

#### Ruby

```ruby
require 'easypost'

client = EasyPost::Client.new(api_key: 'EASYPOST_API_KEY')

webhook = client.webhook.retrieve('hook_...')

client.webhook.delete(webhook.id)
```

Delete a `Webhook` by `id`.