Maxmove LogoDocs

Die Entwicklerdokumentation ist auf Englisch verfügbar.

Sync shop orders into deliveries

An end-to-end recipe: book a Maxmove delivery when a shop order is ready, keep the order updated from webhooks, and never book twice.

Als Markdown ansehen

This recipe connects any shop, ERP, or order system to Maxmove. If you run Shopify, WooCommerce, or Shopware, the ready-made integrations do this for you.

Code examples use BASE_URL and headers from the Quickstart. The PHP examples use Guzzle with the API key set as a default header.

The flow#

  1. Order is ready to ship

    Trigger the booking from your own event, for example "paid" or "packed". Optionally quote first if you show the delivery price to a person before booking.

  2. Book the delivery once

    Use your order id as Idempotency-Key and your order number as external_id. A retry after a timeout then returns the same delivery instead of booking a second courier.

  3. Store the delivery on the order

    Save id (del_…) and tracking_url on your order, and send the tracking link to your customer.

  4. Update the order from webhooks

    Map data.status from each webhook to your order status, and fetch proof of delivery when it arrives.

Book the delivery#

async function bookDelivery(order: ShopOrder) {
  const res = await fetch(`${BASE_URL}/deliveries`, {
    method: 'POST',
    headers: { ...headers, 'Idempotency-Key': `shop-order-${order.id}` },
    body: JSON.stringify({
      external_id: order.number,
      vehicle_type_id: 'courier',
      pickup: {
        address: 'Ehrenstraße 15, 50672 Köln',
        latitude: 50.9385,
        longitude: 6.9469,
        contact: { name: 'Feinkost Ehrenfeld', phone: '+49 221 1234567' },
        instructions: `Order ${order.number}, packed at the counter.`,
      },
      dropoff: {
        address: order.shippingAddress.formatted,
        latitude: order.shippingAddress.latitude,
        longitude: order.shippingAddress.longitude,
        contact: { name: order.customer.name, phone: order.customer.phone },
        instructions: order.deliveryNote ?? undefined,
      },
      items: order.lines.map((line) => ({
        source_line_id: line.id,
        description: line.title,
        quantity: line.quantity,
        weight_kg: line.weightKg,
        length_cm: line.lengthCm,
        width_cm: line.widthCm,
        height_cm: line.heightCm,
        sku: line.sku,
      })),
    }),
  });
 
  const body = await res.json();
  if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.request_id})`);
  await saveDeliveryOnOrder(order.id, { deliveryId: body.id, trackingUrl: body.tracking_url });
}

Your shop must store coordinates with the shipping address. Maxmove does not geocode; use the coordinates your checkout's address autocomplete returns.

Map statuses to your order#

Maxmove data.statusTypical order status
pending, courier_assigned, at_pickupReady for pickup
picked_up, in_transit, at_dropoffOut for delivery
deliveredDelivered
cancelledDelivery failed: needs action

Webhooks can arrive out of order and more than once. Store the event id you processed and only move your order forward.

const RANK = {
  pending: 0, courier_assigned: 1, at_pickup: 2, picked_up: 3,
  in_transit: 4, at_dropoff: 5, delivered: 6, cancelled: 6,
};
 
export async function handleMaxmoveEvent(event: MaxmoveEvent) {
  if (await alreadyProcessed(event.id)) return;
  const order = await findOrderByDeliveryId(event.data.id);
  if (order && RANK[event.data.status] >= RANK[order.deliveryStatus]) {
    await updateOrderDeliveryStatus(order.id, event.data.status);
  }
  if (event.type === 'delivery.pod_submitted') await storeProof(event.data.id);
  await markProcessed(event.id);
}

Verify the signature before you call the handler; see Verify signatures.

Handle the edge cases#

  • Customer cancels the order: cancel the delivery while it is pending, courier_assigned, or at_pickup. See Cancellations.
  • Delivery ends as cancelled: flag the order for a person. Booking again needs a new Idempotency-Key, for example shop-order-1001-2, and a new external_id such as 1001-2, because external_id is unique per mode (409 duplicate_external_id).
  • Address outside the service area: the create request fails with 400 service_area_unavailable. Offer another shipping method at checkout.
  • Too much for the vehicle: 400 vehicle_does_not_fit. Retry with a larger vehicle type; see Items and vehicle fit.

Hat das Ihre Frage beantwortet?