EasyRoutes Webhooks API (Beta)

EasyRoutes API is currently in closed beta. Please contact us if you are interested in early access!

EasyRoutes webhooks support allows you to receive events programmatically in response to changes in your routes. In combination with EasyRoutes API support, webhook events allow you to build powerful automation triggered by deliveries, route updates, or other events.

Create a webhook handler

To receive webhooks, you need a server with an HTTPS endpoint that can accept POST webhook requests. Relevant webhook metadata like the topic and affected object ID are sent via headers as well as in the JSON body of the request. The affected object itself (i.e. a Route or RouteStop ) is sent in the payload field of the body. See Webhook event overview below for details on the request format.

Your webhook handler should return a successful status code (2xx ) to acknowledge the webhook quickly. Avoid doing any complex logic that could cause a timeout synchronously in your endpoint handler.

Webhook event overview

All EasyRoutes webhook requests share a set of common fields, regardless of API version. These fields are serialized in a JSON payload in the body of the POST request.

Field Description Example
eventId The unique ID of the webhook event. In rare cases of duplicate webhook delivery, this can be used to deduplicate work. "evt-fa75b82d-2411-4cf8-b15f-cf3d8c91e919"
topic Topic of the webhook. See Webhook topics for a list of supported topics. "ROUTE_UPDATED"
eventTimestamp The timestamp of the event that triggered the webhook. "2024-08-23T16:12:26-04:00"
apiVersion The API version of the payload object. "V2024_07"
objectId (optional) The ID of the affected object (i.e. Route ID). "rte-6ffc8a79-e556-4ca4-9246-33850da3cedc"
payload (optional) The affected object (i.e. Route ). {"id":"rte-...", "name":"#878", "start":{...}, ...}

Additional some of the metadata fields are mirrored in HTTP headers for convenience.

  • X-EasyRoutes-Event-Id
  • X-EasyRoutes-Topic
  • X-EasyRoutes-Event-Timestamp
  • X-EasyRoutes-Version
  • X-EasyRoutes-Object-Id

Register a webhook endpoint

To register your webhook endpoint, navigate to Settings > API Settings and click Register webhook .

In the creation modal, you can specify the URL of the server endpoint you want to receive webhooks on. The API version determines the format of the data payloads you receive in the body of the webhook. Additionally, you must specify one or more topics that you wish to receive events for (see Webhook topics below for a description of each event and the expected payload).

Creating your first webhook will also generate a Webhook secret that will be used to sign all requests to your endpoint. See Verifying webhook requests for details on how to secure your endpoint.

Testing your webhook

Once you've built your webhook endpoint and registered it in EasyRoutes, you can send a test event (topic = TEST ) to verify everything is working as expected.

Verifying webhook requests

EasyRoutes webhooks are signed using your unique Webhook secret from Settings. The HMAC SHA256 hash is sent in the webhook request via the X-EasyRoutes-Hmac-Sha256 header. To verify that requests are sent by EasyRoutes, compute your own signature from the request body and verify it against the header signature.

Note: your webhook secret is distinct from your API secret key and is always available on the Settings page.

Example webhook endpoint

Here is an example endpoint implement in Go:

package main

import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/base64"
  "encoding/json"
  "io"
  "log"
  "net/http"
)

// Webhook secret key example.
// Not recommend to hardcode!
const easyroutesSecret = "PjV...w=="

type Event struct {
  EventId        string `json:"eventId"`
  Topic          string `json:"topic"`
  EventTimestamp string `json:"eventTimestamp"`
  ApiVersion     string `json:"apiVersion"`
  ObjectId       string `json:"objectId,omitempty"`
  Payload        []byte `json:"payload,omitempty"`
}

func main() {
  http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
    b, err := io.ReadAll(r.Body)
    if err != nil {
      log.Printf("error reading webhook body: %v", err)
      w.WriteHeader(http.StatusServiceUnavailable)
      return
    }

    sig := r.Header.Get("X-EasyRoutes-Hmac-Sha256")
    if len(sig) == 0 {
      log.Printf("missing signature header")
      w.WriteHeader(http.StatusBadRequest)
      return
    }

    d, err := base64.StdEncoding.DecodeString(easyroutesSecret)
    if err != nil {
      log.Fatalf("error decoding secret: %v", err)
    }

    enc := hmac.New(sha256.New, d)
    enc.Write(b)
    expected := enc.Sum(nil)
    actual, err := base64.StdEncoding.DecodeString(sig)
    if err != nil {
      log.Printf("error decoding signature: %v", err)
      w.WriteHeader(http.StatusBadRequest)
      return
    }

    if !hmac.Equal(expected, actual) {
      log.Printf(
        "signature mismatch, expected=%s, actual=%s",
        base64.StdEncoding.EncodeToString(expected),
        base64.StdEncoding.EncodeToString(actual),
      )
      w.WriteHeader(http.StatusBadRequest)
      return
    }

    parsed := &Event{}
    err = json.Unmarshal(b, parsed)
    if err != nil {
      log.Printf("error parsing webhook body: %v", err)
      w.WriteHeader(http.StatusBadRequest)
      return
    }

    switch parsed.Topic {
    case "ROUTE_CREATED":
      // process event here
    }

    w.WriteHeader(http.StatusOK)
  })

  http.ListenAndServe(":8080", nil)
}

Webhook topics

Topic Description Payload type
TEST Test topic generated when testing webhooks from settings. none
ROUTE_CREATED New route created. Route
ROUTE_UPDATED Route updated by planner (i.e. stop order changed, start time, etc) Route
ROUTE_DELETED Route deleted. Route
ROUTE_DISPATCHED Route dispatched to a specific driver or available to claim. Route
ROUTE_STARTED Route started by driver. Route
STOP_STATUS_UPDATED

An individual route stop status has changed.

This will trigger for all stops when the route is marked as ready.

Individual stops are triggered as their status is updated during delivery (i.e. out for delivery, attempted, delivered, etc).

Break stops will also trigger this event when completed.

RouteStop
ROUTE_COMPLETED

Route completed by driver.

For a route that ends at the last stop, this is triggered when all stops are complete (attempted or delivered).

For a route with an explicit end stop, this is triggered when the route is marked complete.

Route
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.