Webhooks

Get notified when something happens to your orders - like they get picked up or delivered. Instead of constantly checking your panel, we'll tell you.

How to set up

Here's what you need to do:

  1. Create an endpoint in your app to receive notifications
  2. Register your URL in your 4N NextDay panel
  3. Verify signatures to make sure they're really from us
  4. Respond quickly with a 2xx status

What we'll notify you about

You'll get notified when:

  • Name
    order.received
    Description

    Your package was scanned at our warehouse.

  • Name
    order.status_changed
    Description

    Order status changed (picked up, in transit, delivered, etc.).

  • Name
    order.delivered
    Description

    Order was successfully delivered. Also fires order.status_changed.

  • Name
    order.delivery_failed
    Description

    A delivery attempt failed. Also fires order.status_changed.

When you'll hear from us

EventWhen it fires
order.receivedPackage arrives at our warehouse
order.status_changedStatus updates - same as what you see in your panel
order.deliveredPackage was delivered successfully (also fires order.status_changed)
order.delivery_failedDelivery attempt failed (also fires order.status_changed)

order.delivered event

{
  "event": "order.delivered",
  "timestamp": "2025-02-04T11:30:00.000000Z",
  "data": {
    "tracking_number": "4N000000012345",
    "external_reference": "ORDER-001",
    "cost_center": "CC-001",
    "purchase_order": "PO-2025-100",
    "bill_of_lading": null,
    "delivery_state": "delivered",
    "previous_state": "out_for_delivery",
    "estimated_delivery_date": "2025-02-04T23:59:59.000000Z",
    "delivery_date": "2025-02-04T11:30:00.000000Z",
    "timeline": ["..."],
    "delivery_proof": {"..."}
  }
}

Delivery states

The delivery_state and previous_state fields in webhook payloads use these values - the same statuses you see in your panel:

PendingPicked UpIn TransitOut for DeliveryDeliveredFailedCancelled
StateDescription
pendingOrder created, awaiting pickup
picked_upPackage collected from sender
in_transitIn transport to destination area
out_for_deliveryOn delivery vehicle for final delivery
deliveredSuccessfully delivered (final)
failedDelivery attempt failed
nulledOrder cancelled (final)

What you'll receive

Headers we send

Every webhook includes:

HeaderDescription
Content-Typeapplication/json
X-4Nortes-EventThe event type (e.g., order.status_changed)
X-4Nortes-SignatureHMAC signature for verification

When we receive your package

Sent when your package is scanned at our warehouse.

  • Name
    event
    Type
    string
    Description

    Event type: order.received

  • Name
    timestamp
    Type
    string
    Description

    ISO 8601 timestamp of the event.

  • Name
    data.tracking_number
    Type
    string
    Description

    The order tracking number.

  • Name
    data.external_reference
    Type
    string
    Description

    Your custom order reference.

  • Name
    data.cost_center
    Type
    string|null
    Description

    Cost center associated with the order.

  • Name
    data.purchase_order
    Type
    string|null
    Description

    Purchase order number.

  • Name
    data.bill_of_lading
    Type
    string|null
    Description

    Bill of lading reference.

  • Name
    data.delivery_state
    Type
    string
    Description

    Current delivery state.

  • Name
    data.estimated_delivery_date
    Type
    string
    Description

    Estimated delivery date (ISO 8601).

  • Name
    data.delivery_date
    Type
    string
    Description

    Actual delivery date, null until delivered.

  • Name
    data.timeline
    Type
    array
    Description

    Timeline events up to this point.

  • Name
    data.reception.received_at
    Type
    string
    Description

    Timestamp when the package was received at the warehouse.

  • Name
    data.reception.warehouse_id
    Type
    integer
    Description

    ID of the warehouse that received the package.

order.received payload

{
  "event": "order.received",
  "timestamp": "2025-02-03T14:30:00.000000Z",
  "data": {
    "tracking_number": "4N000000012345",
    "external_reference": "ORDER-001",
    "cost_center": "CC-001",
    "purchase_order": "PO-2025-100",
    "bill_of_lading": null,
    "delivery_state": "pending",
    "estimated_delivery_date": "2025-02-05T23:59:59.000000Z",
    "delivery_date": null,
    "timeline": [
      {
        "state": "pending",
        "timestamp": "2025-02-03T10:00:00.000000Z"
      }
    ],
    "reception": {
      "received_at": "2025-02-03T14:30:00.000000Z",
      "warehouse_id": 1
    }
  }
}

When order status changes

Sent whenever the delivery status updates - same events you'd see in your panel.

  • Name
    event
    Type
    string
    Description

    Event type: order.status_changed

  • Name
    timestamp
    Type
    string
    Description

    ISO 8601 timestamp of the event.

  • Name
    data.tracking_number
    Type
    string
    Description

    The order tracking number.

  • Name
    data.external_reference
    Type
    string
    Description

    Your custom order reference.

  • Name
    data.cost_center
    Type
    string|null
    Description

    Cost center associated with the order.

  • Name
    data.purchase_order
    Type
    string|null
    Description

    Purchase order number.

  • Name
    data.bill_of_lading
    Type
    string|null
    Description

    Bill of lading reference.

  • Name
    data.delivery_state
    Type
    string
    Description

    New delivery state.

  • Name
    data.previous_state
    Type
    string
    Description

    Previous delivery state.

  • Name
    data.estimated_delivery_date
    Type
    string
    Description

    Updated estimated delivery date.

  • Name
    data.delivery_date
    Type
    string
    Description

    Actual delivery date (if delivered).

  • Name
    data.timeline
    Type
    array
    Description

    Full timeline of state changes. Each entry has state and timestamp. Failure entries also include type and failure_info.

  • Name
    data.delivery_proof
    Type
    object
    Description

    Proof of delivery (only for delivered state). Includes recipient_name, recipient_rut, recipient_role, location, notes, photos, and created_at.

order.status_changed payload

{
  "event": "order.status_changed",
  "timestamp": "2025-02-04T11:30:00.000000Z",
  "data": {
    "tracking_number": "4N000000012345",
    "external_reference": "ORDER-001",
    "cost_center": "CC-001",
    "purchase_order": "PO-2025-100",
    "bill_of_lading": null,
    "delivery_state": "delivered",
    "previous_state": "out_for_delivery",
    "estimated_delivery_date": "2025-02-04T23:59:59.000000Z",
    "delivery_date": "2025-02-04T11:30:00.000000Z",
    "timeline": [
      {
        "state": "pending",
        "timestamp": "2025-02-03T10:00:00.000000Z"
      },
      {
        "state": "in_transit",
        "timestamp": "2025-02-03T18:00:00.000000Z"
      },
      {
        "state": "out_for_delivery",
        "timestamp": "2025-02-04T08:00:00.000000Z"
      },
      {
        "state": "delivered",
        "timestamp": "2025-02-04T11:30:00.000000Z"
      }
    ],
    "delivery_proof": {
      "recipient_name": "Jane Doe",
      "recipient_rut": "12345678-9",
      "recipient_role": "recipient",
      "location": {
        "latitude": -33.4289,
        "longitude": -70.6093
      },
      "notes": null,
      "photos": [
        {
          "url": "https://storage.example.com/photos/abc123.jpg",
          "preview_url": "https://storage.example.com/photos/thumb/abc123.jpg"
        }
      ],
      "created_at": "2025-02-04T11:30:00.000000Z"
    }
  }
}

When order is delivered

Sent when an order is successfully delivered. This event fires in addition to order.status_changed, so you don't need to handle both - use whichever fits your integration.

  • Name
    event
    Type
    string
    Description

    Event type: order.delivered

  • Name
    timestamp
    Type
    string
    Description

    ISO 8601 timestamp of the event.

  • Name
    data.tracking_number
    Type
    string
    Description

    The order tracking number.

  • Name
    data.external_reference
    Type
    string
    Description

    Your custom order reference.

  • Name
    data.cost_center
    Type
    string|null
    Description

    Cost center associated with the order.

  • Name
    data.purchase_order
    Type
    string|null
    Description

    Purchase order number.

  • Name
    data.bill_of_lading
    Type
    string|null
    Description

    Bill of lading reference.

  • Name
    data.delivery_state
    Type
    string
    Description

    Always delivered.

  • Name
    data.previous_state
    Type
    string
    Description

    Previous delivery state (e.g., out_for_delivery).

  • Name
    data.estimated_delivery_date
    Type
    string
    Description

    Estimated delivery date (ISO 8601).

  • Name
    data.delivery_date
    Type
    string
    Description

    Actual delivery date (ISO 8601).

  • Name
    data.timeline
    Type
    array
    Description

    Full timeline of state changes.

  • Name
    data.delivery_proof
    Type
    object
    Description

    Proof of delivery. Includes recipient_name, recipient_rut, recipient_role, location, notes, photos, and created_at.

order.delivered payload

{
  "event": "order.delivered",
  "timestamp": "2025-02-04T11:30:00.000000Z",
  "data": {
    "tracking_number": "4N000000012345",
    "external_reference": "ORDER-001",
    "cost_center": "CC-001",
    "purchase_order": "PO-2025-100",
    "bill_of_lading": null,
    "delivery_state": "delivered",
    "previous_state": "out_for_delivery",
    "estimated_delivery_date": "2025-02-04T23:59:59.000000Z",
    "delivery_date": "2025-02-04T11:30:00.000000Z",
    "timeline": [
      {
        "state": "pending",
        "timestamp": "2025-02-03T10:00:00.000000Z"
      },
      {
        "state": "in_transit",
        "timestamp": "2025-02-03T18:00:00.000000Z"
      },
      {
        "state": "out_for_delivery",
        "timestamp": "2025-02-04T08:00:00.000000Z"
      },
      {
        "state": "delivered",
        "timestamp": "2025-02-04T11:30:00.000000Z"
      }
    ],
    "delivery_proof": {
      "recipient_name": "Jane Doe",
      "recipient_rut": "12345678-9",
      "recipient_role": "recipient",
      "location": {
        "latitude": -33.4289,
        "longitude": -70.6093
      },
      "notes": null,
      "photos": [
        {
          "url": "https://storage.example.com/photos/abc123.jpg",
          "preview_url": "https://storage.example.com/photos/thumb/abc123.jpg"
        }
      ],
      "created_at": "2025-02-04T11:30:00.000000Z"
    }
  }
}

When delivery fails

Sent when a delivery attempt fails. This event fires in addition to order.status_changed, so you don't need to handle both - use whichever fits your integration.

  • Name
    event
    Type
    string
    Description

    Event type: order.delivery_failed

  • Name
    timestamp
    Type
    string
    Description

    ISO 8601 timestamp of the event.

  • Name
    data.tracking_number
    Type
    string
    Description

    The order tracking number.

  • Name
    data.external_reference
    Type
    string
    Description

    Your custom order reference.

  • Name
    data.cost_center
    Type
    string|null
    Description

    Cost center associated with the order.

  • Name
    data.purchase_order
    Type
    string|null
    Description

    Purchase order number.

  • Name
    data.bill_of_lading
    Type
    string|null
    Description

    Bill of lading reference.

  • Name
    data.delivery_state
    Type
    string
    Description

    Always failed.

  • Name
    data.previous_state
    Type
    string
    Description

    Previous delivery state (e.g., out_for_delivery).

  • Name
    data.estimated_delivery_date
    Type
    string
    Description

    Estimated delivery date (ISO 8601).

  • Name
    data.delivery_date
    Type
    string
    Description

    Always null (delivery did not complete).

  • Name
    data.timeline
    Type
    array
    Description

    Full timeline of state changes. The last entry includes failure_info.

  • Name
    data.failure_info
    Type
    object
    Description

    Details about the failure. Includes type, reason, and notes.

order.delivery_failed payload

{
  "event": "order.delivery_failed",
  "timestamp": "2025-02-04T14:00:00.000000Z",
  "data": {
    "tracking_number": "4N000000012345",
    "external_reference": "ORDER-001",
    "cost_center": "CC-001",
    "purchase_order": "PO-2025-100",
    "bill_of_lading": null,
    "delivery_state": "failed",
    "previous_state": "out_for_delivery",
    "estimated_delivery_date": "2025-02-04T23:59:59.000000Z",
    "delivery_date": null,
    "timeline": [
      {
        "state": "pending",
        "timestamp": "2025-02-03T10:00:00.000000Z"
      },
      {
        "state": "in_transit",
        "timestamp": "2025-02-03T18:00:00.000000Z"
      },
      {
        "state": "out_for_delivery",
        "timestamp": "2025-02-04T08:00:00.000000Z"
      },
      {
        "state": "failed",
        "timestamp": "2025-02-04T14:00:00.000000Z",
        "type": "delivery_failed",
        "failure_info": {
          "type": "recipient_not_found",
          "reason": "No se encontró al destinatario",
          "notes": null
        }
      }
    ],
    "failure_info": {
      "type": "recipient_not_found",
      "reason": "No se encontró al destinatario",
      "notes": null
    }
  }
}

Make sure it's really from us

Verify the signature in the X-4Nortes-Signature header to confirm we sent it.

How to verify

We sign every webhook with your secret key. Check it like this:

Verifying webhook signatures

const crypto = require('crypto')

function verifyWebhook(payload, signature, secret) {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(hash),
    Buffer.from(signature)
  )
}

// Express.js example
app.post('/webhooks/nextday', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-4nortes-signature']
  const payload = req.body.toString()

  if (!verifyWebhook(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature')
  }

  const event = JSON.parse(payload)
  // Process the event...

  res.status(200).send('OK')
})

Always use constant-time comparison functions (like crypto.timingSafeEqual or hash_equals) to prevent timing attacks.


If your server is down

Don't worry - we'll retry if you don't respond with a 2xx:

AttemptDelay
1st retry~10 seconds
2nd retry~2 minutes
3rd retry~17 minutes
4th retry~3 hours

After 5 total attempts (1 initial + 4 retries), we'll give up on that webhook.

Tips

  1. Respond fast: Return 2xx within 10 seconds
  2. Process later: Save the data and handle it in the background
  3. Handle duplicates: You might get the same webhook twice
  4. Keep logs: Save events for debugging
  5. Monitor failures: Set up alerts if webhooks stop working

Async processing example

app.post('/webhooks/nextday', async (req, res) => {
  // Acknowledge receipt immediately
  res.status(200).send('OK')

  // Process asynchronously
  const event = req.body
  await queue.add('processWebhook', event)
})

Was this page helpful?