> ## Documentation Index
> Fetch the complete documentation index at: https://terminal49.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Set Up Webhooks

> Create a Terminal49 webhook endpoint, subscribe to shipment events, verify HMAC signatures, and handle retries so your consumer stays reliable.

This guide shows how to configure a production webhook consumer for Terminal49 tracking updates.

For background on why Terminal49 recommends webhooks instead of polling, see [Why Webhooks](/api-docs/webhooks/overview). For event names and payload details, use the [Event Catalog](/api-docs/webhooks/event-catalog) and [Webhook Payloads](/api-docs/webhooks/payloads) references.

## Prerequisites

You need:

* A Terminal49 API key from the [developer portal](https://app.terminal49.com/developers/api-keys).
* A public HTTPS endpoint that accepts `POST` requests.
* A list of events your integration should receive.
* A place to store the webhook secret securely.

Use the [List Webhook Events](/api-docs/api-reference/webhooks/list-webhook-events) endpoint to fetch the event categories available to your account. Use the [Trigger Webhook](/api-docs/api-reference/webhooks/trigger-a-webhook) endpoint to send a sample event payload to an HTTPS URL while you test your handler.

## Create the webhook

You can create a webhook from the dashboard or API.

To use the dashboard, open [Developer Webhooks](https://app.terminal49.com/developers/webhooks) and click **Create Webhook Endpoint**.

To use the API:

```bash theme={null}
curl -X POST "https://api.terminal49.com/v2/webhooks" \
  -H "Authorization: Token YOUR_API_KEY" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "webhook",
      "attributes": {
        "url": "https://example.com/webhooks/terminal49",
        "active": true,
        "events": [
          "tracking_request.succeeded",
          "tracking_request.failed",
          "container.updated"
        ]
      }
    }
  }'
```

Subscribe only to the events your integration handles. Use the [Event Catalog](/api-docs/webhooks/event-catalog) for the canonical event list.

## Store the webhook secret

The webhook response includes a `secret` attribute. Store it in your secrets manager.

Terminal49 signs every webhook delivery with an HMAC SHA-256 digest of the raw request body. The digest is sent in the `X-T49-Webhook-Signature` header.

## Verify the signature

Verify the signature before parsing or trusting the JSON body.

<CodeGroup>
  ```ruby Ruby theme={null}
  def valid_signature?(raw_body, signature, secret)
    digest = OpenSSL::HMAC.hexdigest("SHA256", secret, raw_body)
    Rack::Utils.secure_compare(digest, signature.to_s)
  end
  ```

  ```javascript Node.js theme={null}
  import crypto from "crypto";

  function validSignature(rawBody, signature, secret) {
    const digest = crypto
      .createHmac("sha256", secret)
      .update(rawBody)
      .digest("hex");

    const received = Buffer.from(signature || "", "hex");
    const expected = Buffer.from(digest, "hex");

    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  }
  ```

  ```python Python theme={null}
  import hashlib
  import hmac

  def valid_signature(raw_body: bytes, signature: str, secret: str) -> bool:
      digest = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(signature or "", digest)
  ```

  ```go Go theme={null}
  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/hex"
  )

  func validSignature(rawBody []byte, signature string, secret string) bool {
  	mac := hmac.New(sha256.New, []byte(secret))
  	mac.Write(rawBody)
  	expected := hex.EncodeToString(mac.Sum(nil))
  	return hmac.Equal([]byte(signature), []byte(expected))
  }
  ```
</CodeGroup>

## Allowlist Terminal49 webhook IPs

Fetch the current source IP list from the [List Webhook IPs](/api-docs/api-reference/webhooks/list-webhook-ips) endpoint and allowlist those addresses in your firewall or application.

Cache the list and refresh it periodically. Do not rely on a hard-coded list in application code.

## Accept the notification

Your endpoint should return `200`, `201`, `202`, or `204` after it safely accepts the event. Any other response, including a timeout, triggers retries.

Terminal49 retries failed deliveries up to 12 times before marking the notification as failed.

```javascript theme={null}
app.post("/webhooks/terminal49", express.raw({ type: "*/*" }), async (req, res) => {
  const signature = req.header("X-T49-Webhook-Signature");

  if (!validSignature(req.body, signature, process.env.T49_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }

  const payload = JSON.parse(req.body.toString("utf8"));
  await enqueueWebhook(payload);

  res.sendStatus(202);
});
```

## Handle duplicate deliveries

Retries can deliver the same notification more than once. Use `data.id` as the idempotency key for your processing log.

```javascript theme={null}
async function processWebhook(payload) {
  const notificationId = payload.data.id;

  if (await alreadyProcessed(notificationId)) {
    return;
  }

  await handleEvent(payload);
  await markProcessed(notificationId);
}
```

## Monitor failed notifications

Use the [Webhook Notifications API](/api-docs/api-reference/webhook-notifications/list-webhook-notifications) to review recent deliveries and spot the ones marked `delivery_status: failed`:

```bash theme={null}
curl -s "https://api.terminal49.com/v2/webhook_notifications" \
  -H "Authorization: Token YOUR_API_KEY" \
  -H "Content-Type: application/vnd.api+json"
```

Missed notifications cannot be replayed. After you fix the consumer, use [Trigger a Webhook](/api-docs/api-reference/webhooks/trigger-a-webhook) to re-test your handler with a sample payload, then re-fetch the affected shipments and containers from the REST API to backfill any missed state changes.

For more help, see [How to Troubleshoot Missing Webhook Notifications](https://help.terminal49.com/en/articles/7851422-missing-webhook-notifications).

## Related

* [Why Webhooks](/api-docs/webhooks/overview) - conceptual overview
* [Event Catalog](/api-docs/webhooks/event-catalog) - canonical event names
* [Webhook Payloads](/api-docs/webhooks/payloads) - payload envelope and included resources
* [Webhook Best Practices](/api-docs/webhooks/best-practices) - reliability checklist
* [Create a Webhook API Reference](/api-docs/api-reference/webhooks/create-a-webhook) - request and response fields
