Webhooks Guide

Several types of objects are processed asynchronously in the EasyPost system (e.g., Batches, Trackers). To provide updates on these background tasks, EasyPost dispatches webhook events whenever a new Event occurs.

Understanding Webhooks

Webhooks are push notifications (or callbacks) that keep applications up-to-date on the status of EasyPost objects.

  • Webhooks eliminate the need to poll for updates.
  • When triggered, each webhook sends an Event via HTTP POST request to the configured webhook URL.
  • A successful 2XX response is exepected from each webhook; in the event of a failure, EasyPost retries the webhook.

Webhook Examples


Prerequisites

  • An active EasyPost account.
  • Configure webhook URLs in the EasyPost account page.
  • Application readiness to handle and process webhook events.
  • Use HMAC validation or basic authentication with HTTPS to secure the webhook 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.

Use validate_webhook() from EasyPost’s client libraries (the function name may vary depending on the programming language–refer to the respective library documentation for the exact name). Pass the webhook secret, headers, and event body to this function. If the signature matches, the webhook is valid; otherwise, the event should be rejected to prevent fraudulent data.

Basic Authentication

Basic authentication requires a username and password in the webhook URL. For example:

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

EasyPost will deliver a webhook to this endpoint, including an Authorization header. Validate the credentials to ensure they match stored values; otherwise, reject the webhook.

Create a Webhook

POST /webhooks
1curl -X POST https://api.easypost.com/v2/webhooks \
2  -u "EASYPOST_API_KEY": \
3  -H 'Content-Type: application/json' \
4  -d '{
5    "webhook": {
6      "url": "example.com"
7    }
8  }'

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.

Webhooks sent over outdated or insecure protocols, such as SSLv2 or SSLv3, are not supported. Additionally, any connection using export-grade ciphers will be rejected.

Refer to Mozilla's guide to Server-Side TLS and Qualys's SSL/TLS Deployment Best Practices for guidance on properly securing server configurations.


Event Filtering and Safelisting

Event Type Header

To streamline event handling, EasyPost adds an X-Easypost-Event-Type header to webhook requests. Load balancers can use this header to filter out non-essential events without parsing the entire payload, reducing infrastructure load.


Custom Outbound headers

EasyPost supports custom headers on outbound webhook requests, allowing up to three custom headers, each with a name and value under 1000 ASCII characters. The header names and values are stored and returned as plaintext.

This feature allows customers to meet specific API gateway needs, such as load balancing or custom authorization checks, for more flexible integrations.

Naming Conventions and Format

The custom_headers attribute in the webhook object is a list of key-value pairs, where each entry consists of:

  • <header_name> and <header_value> must be valid strings, with a character limit of 1000.
  • Each <header_name> must be unique within a webhook request.

Validation Requirements

  • Custom headers must be specified as an array of key-value pairs.
  • If the custom_headers attribute is included in a POST or PATCH request, both name and value must be set for each header.
  • Only ASCII characters are allowed in both <header_name> and <header_value>.

Blocked Header Names and Prefixes

Certain headers are restricted to prevent conflicts with system-defined values:

  • Headers prefixed with X-EasyPost-* cannot be used. Any attempt to set a custom header with this prefix will result in a failure.
  • Headers using an existing X-* standard name or non-standard X-* headers will be overwritten in case of a conflict by EasyPost during webhook dispatch.

Security

  • Custom header values are stored in plaintext in EasyPost’s secure database. Encryption is not applied, so it is strongly recommended to use pre-encrypted values where necessary.
  • Headers should not contain sensitive information unless properly secured before transmission.

Updating Custom Headers

  • Custom headers can be modified using a PATCH request.
  • The entire list of headers will be replaced with the new list provided in the request. Individual additions or deletions of specific headers are not supported.

RFC Compliance

All custom header names must conform to RFC 7230/ RFC 9112 formatting rules to ensure compatibility with standard HTTP header specifications.


Response Time

Webhooks require a response within seven seconds. If no response is received, EasyPost will automatically retry the webhook.

Recommendation: Process the event in a background worker and immediately return a 2XX status code to avoid duplicate notifications.


Retrying Failed Webhooks

EasyPost will retry failed webhook deliveries up to six times, with an increasing delay between retries.


Handling Sensitive Data

Avoid exposing sensitive information within webhook payloads. Secure transmission methods such as HTTPS and encryption are strongly recommended.


Testing Webhook Integrations

To understand webhook event contents, use an endpoint mocking service like Beeceptor to create a simple endpoint for inspecting webhook requests.

Note: EasyPost does not endorse Beeceptor; verify its security measures before sending actual user data.


FAQs

Q: How many times will EasyPost attempt to deliver a webhook?

A: EasyPost will retry failed webhooks six times, with increasing delay intervals between attempts.

Q: What HTTP status code is required for webhooks?

A: A 2XX status code is required, with 200 OK or 200 Accepted preferred.

Q: How long is there to respond to a webhook?

A: Respond within seven seconds. If no response is sent, the webhook event is considered a failure and will be retried.

Q: How can webhook integrations be tested?

A: Webhooks triggered in the Test environment behave the same way as in Production. Use services like Beeceptor to mock public endpoints for local testing.

Q: Can webhooks be received for packages not shipped through EasyPost?

Yes, create an EasyPost Tracker object using the desired tracking code to receive webhooks for non-EasyPost shipments.

Q: How many webhook endpoints can be designated?

Most users configure 1–5 endpoints, but up to 30 endpoints can be designated. If more are needed, contact EasyPost support.


Webhook Examples

Webhook POST JSON Example

{
  "mode": "production",
  "description": "batch.created",
  "previous_attributes": { "state": "purchasing" },
  "pending_urls": ["example.com/easypost-webhook"],
  "completed_urls": [],
  "created_at": "2015-12-03T19:09:19Z",
  "updated_at": "2015-12-03T19:09:19Z",
  "result": {
    "id": "batch_...",
    "object": "Batch",
    "mode": "production",
    "state": "purchased",
    "num_shipments": 1,
    "reference": null,
    "created_at": "2015-12-03T19:09:19Z",
    "updated_at": "2015-12-03T19:09:19Z",
    "scan_form": null,
    "shipments": [
      {
        "batch_status": "postage_purchased",
        "batch_message": null,
        "id": "shp_a5b1348307694736aaqqqq8fqda53f93"
      }
    ],
    "status": {
      "created": 0,
      "queued_for_purchase": 0,
      "creation_failed": 0,
      "postage_purchased": 1,
      "postage_purchase_failed": 0
    },
    "pickup": null,
    "label_url": null
  },
  "id": "evt_...",
  "object": "Event"
}

Retrieve a Webhook Event JSON Example

{
  "description": "tracker.updated",
  "mode": "test",
  "previous_attributes": {
    "status": "pre_transit"
  },
  "created_at": "2022-10-26T20:18:21.000Z",
  "pending_urls": [],
  "completed_urls": [],
  "updated_at": "2022-10-26T20:18:21.000Z",
  "id": "evt_55a53eb2556b11ed8945059f515d2b6d",
  "user_id": "user_060ab38db3c04ffaa60f262e5781a9be",
  "status": "pending",
  "object": "Event"
}

Receiving a Webhook Example

require 'easypost'
require 'sinatra'

post '/easypost-webhook' do
  result = params['result']

  case result['object']
  when 'Batch'
    batch = client.batch.create(result)

    case batch.state
    when 'purchase_failed'
      batch.shipments.each do |shipment|
        if shipment.batch_status == 'postage_purchase_failed'
          client.batch.remove_shipments(batch.id, shipments: [shipment])
        end
      end
    end
  end
end

Addiitonal Resources

Please visit the Help Center for more information.