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.).

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

Event type example

{
  "event": "order.status_changed",
  "timestamp": "2025-02-04T11:30:00.000000Z",
  "data": {
    "tracking_number": "4N000000012345",
    "delivery_state": "delivered",
    "previous_state": "out_for_delivery"
  }
}

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
X-4Nortes-TimestampUnix timestamp of the event

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.received_at
    Type
    string
    Description

    Timestamp when package was received.

  • Name
    data.warehouse
    Type
    string
    Description

    Warehouse location 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",
    "received_at": "2025-02-03T14:30:00.000000Z",
    "warehouse": "Santiago Distribution Center"
  }
}

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.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

    Recent timeline events.

  • Name
    data.delivery_proof
    Type
    object
    Description

    Proof of delivery (only for delivered state).

order.status_changed payload

{
  "event": "order.status_changed",
  "timestamp": "2025-02-04T11:30:00.000000Z",
  "data": {
    "tracking_number": "4N000000012345",
    "external_reference": "ORDER-001",
    "delivery_state": "delivered",
    "previous_state": "out_for_delivery",
    "estimated_delivery_date": "2025-02-04",
    "delivery_date": "2025-02-04",
    "timeline": [
      {
        "state": "delivered",
        "timestamp": "2025-02-04T11:30:00.000000Z",
        "type": "state_change",
        "failure_info": null
      }
    ],
    "delivery_proof": {
      "recipient_name": "Jane Doe",
      "recipient_rut": "12345678-9",
      "recipient_role": "recipient",
      "notes": null,
      "location": {
        "latitude": -33.4289,
        "longitude": -70.6093
      },
      "photos": [
        {
          "url": "https://storage.example.com/photos/abc123.jpg",
          "preview_url": "https://storage.example.com/photos/thumb/abc123.jpg"
        }
      ]
    }
  }
}

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 retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 hours

After 5 tries, 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?