Skip to content

Customers ​

Every order is delivered to a customer who has at least one address. Customers are scoped to your merchant account — phone numbers and emails are de-duplicated within your account and per customer type, so the same phone number can exist once as an individual customer and once as a business customer.

Create a customer ​

POST /integration/merchant/customer ​

Request body:

json
{
  "name": "Sara Ahmad",
  "phoneNumber": "+96550001234",
  "email": "[email protected]",
  "addresses": [
    {
      "label": "Home",
      "addressType": "home",
      "governorateId": "<governorate-id>",
      "neighborhoodId": "<neighborhood-id>",
      "blockId": "4",
      "streetId": "12",
      "houseNumber": "8",
      "buildingName": "Al-Salam Tower",
      "floorNumber": "3",
      "apartmentNumber": "11"
    }
  ]
}
FieldRequiredNotes
nameYesCustomer full name.
phoneNumberYesValidated and normalized. Must be unique within your merchant account per customer type.
emailNoValidated if provided. Unique within your merchant account per customer type.
customerTypeNoindividual (default) or business. Required to be business for B2B orders.
businessNameBusiness onlyRequired when customerType is business — missing returns 400 VALIDATION_FAILED with errors.businessName.
contactPersonNameNoContact person at the business.
addressesNoUp to 5. The first address becomes the default. label is the only schema-required address field; governorateId and neighborhoodId are required for pricing.

Response: 201 Created

json
{
  "success": true,
  "data": {
    "customer": {
      "_id": "665f...",
      "name": "Sara Ahmad",
      "phoneNumber": "96550001234",
      "addresses": [ { "_id": "665f...", "label": "Home", "isDefault": true, /* ... */ } ]
    }
  }
}

Store customer._id and the relevant addresses[]._id — you need both to create an order.

Find or create a customer (upsert) ​

POST /integration/merchant/customer/upsert ​

Convenience endpoint for the common "do I already know this customer?" flow — ideal for e-commerce checkouts. Looks the customer up by phone number within your merchant account:

  • Found → returns the existing customer — 200 OK, created: false
  • Not found → creates and links a new customer — 201 Created, created: true

Request body:

json
{ "phoneNumber": "+96550001234", "name": "Sara Ahmad", "email": "[email protected]" }
FieldRequiredNotes
phoneNumberYesLookup key. Normalized before matching (see note below).
nameOn createRequired only when no customer matches — send it always so either path succeeds.
emailOptionalStored on creation only.

Response:

json
{ "success": true, "data": { "customer": { /* full customer */ }, "created": true } }

The customer shape is identical in both cases. A newly created customer has no addresses — add one with Add an address before creating orders.

Phone normalization

Phone numbers are normalized before storage and matching: spaces, dashes, the leading + and 00 are stripped, so +965 5000-1234 is stored and matched as 96550001234. Any format you send will match the same customer.

Address fields ​

FieldRequiredDescription
labelSchema requiredA name for the address, e.g. "Home", "Office".
governorateIdRequired for pricingFrom the area tree. Orders cannot be priced without it.
neighborhoodIdRequired for pricingFrom the area tree. Orders cannot be priced without it.
addressTypeOptionalhome (default) | apartment | office.
blockIdOptional — strongly preferredImproves pricing accuracy and routing.
streetIdOptionalStreet within the block.
parcelIdOptionalParcel identifier.
houseNumberOptionalHouse or villa number.
buildingNameOptionalName of the building or tower.
floorNumberOptionalFloor within a building.
apartmentNumberOptionalApartment unit number.
officeNumberOptionalOffice unit number.
paciNumberOptionalKuwait Civil ID (PACI) parcel number.
line1OptionalFree-text fallback address line.
coordinatesOptional{ "lat": <number>, "lng": <number> }.

Required for pricing

At minimum, each address needs governorateId and neighborhoodId — the delivery fee is resolved from them. Provide blockId as well whenever you can. If a customer gives you their Kuwait Civil ID (PACI number), resolve the full hierarchy in one call via GET /governorate-area/civil-id.

List customers ​

GET /integration/merchant/customer ​

Returns your merchant's customers with pagination. The list is always returned under data.list with data.pagination.

Query parameters:

ParameterDescription
page / limitPagination — default 1 / 20.
phoneNumberExact match. Normalized before matching, so any format works (+965 5000-1234 matches 96550001234).
emailExact match, case-insensitive.
namePartial match, case-insensitive.
searchFree-text search across name, email, and phone at once.
customerTypeindividual or business.
governorateId / neighborhoodIdOnly customers with an address in the given area.
activetrue or false.
sortField / sortDirectionDefault createdAt / desc.
bash
curl "https://www.wasal.org/api/v1/integration/merchant/customer?phoneNumber=%2B96550001234" \
  -H "Authorization: Bearer pk_live_YOUR_KEY_HERE"

Response: 200 OK

json
{
  "success": true,
  "data": {
    "list": [
      {
        "_id": "665f1e2a9b3c4d5e6f708192",
        "name": "Sara Ahmad",
        "phoneNumber": "96550001234",
        "email": "[email protected]",
        "active": true,
        "addresses": [
          {
            "_id": "665f1e2a9b3c4d5e6f708193",
            "label": "Home",
            "isDefault": true,
            "governorateId": "<governorate-id>",
            "neighborhoodId": "<neighborhood-id>",
            "blockId": "4",
            "governorateName": "Hawalli",
            "neighborhoodName": "Salmiya",
            "blockName": "Block 4"
          }
        ],
        "defaultAddress": { /* the default address, same shape as above */ },
        "createdAt": "2025-05-20T09:15:00.000Z"
      }
    ],
    "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
  }
}

phoneNumber, email, and name can be combined (AND logic). Use search instead when you want a single query matched against all three fields.

Get a customer ​

GET /integration/merchant/customer/:customerId ​

Returns a single customer (must belong to your merchant), including all addresses.

Query parameters:

ParameterDescription
includeRecentOrderstrue to also return the customer's 5 most recent orders with your merchant under data.recentOrders. Omitted by default.
bash
curl "https://www.wasal.org/api/v1/integration/merchant/customer/665f1e2a9b3c4d5e6f708192?includeRecentOrders=true" \
  -H "Authorization: Bearer pk_live_YOUR_KEY_HERE"

Response: 200 OK

json
{
  "success": true,
  "data": {
    "customer": {
      "_id": "665f1e2a9b3c4d5e6f708192",
      "name": "Sara Ahmad",
      "phoneNumber": "96550001234",
      "email": "[email protected]",
      "addresses": [
        {
          "_id": "665f1e2a9b3c4d5e6f708193",
          "label": "Home",
          "isDefault": true,
          "addressType": "home",
          "governorateId": "<governorate-id>",
          "neighborhoodId": "<neighborhood-id>",
          "blockId": "4",
          "streetId": "12",
          "houseNumber": "8",
          "governorateName": "Hawalli",
          "neighborhoodName": "Salmiya",
          "blockName": "Block 4",
          "streetName": "Street 12"
        }
      ],
      "createdAt": "2025-05-20T09:15:00.000Z",
      "updatedAt": "2025-06-01T10:30:00.000Z"
    },
    "recentOrders": [
      {
        "_id": "6660a1b2c3d4e5f607081920",
        "orderNumber": "ACME-000042",
        "status": "delivered",
        "pricing": { "deliveryFee": 1.5, "orderValue": 12.0 },
        "orderBranchName": "Salmiya Branch",
        "createdAt": "2025-06-01T10:30:00.000Z"
      }
    ]
  }
}

data.recentOrders is only present when includeRecentOrders=true is sent.

Returns 404 CUSTOMER_NOT_FOUND if the customer doesn't exist or is not linked to your merchant.

Update a customer ​

PUT /integration/merchant/customer/:customerId ​

Updates the customer's name, phoneNumber, email, customerType, businessName, or contactPersonName. Only send the fields you want to change.

Request body:

json
{ "name": "Sara A. Al-Rashidi", "email": "[email protected]" }

Response: 200 OK — updated customer object.

Add an address ​

POST /integration/merchant/customer/:customerId/address ​

Adds an address to an existing customer (max 5 total). The first address added to a customer with no addresses becomes the default.

Request body:

json
{
  "label": "Office",
  "governorateId": "<governorate-id>",
  "neighborhoodId": "<neighborhood-id>",
  "blockId": "7",
  "streetId": "3",
  "buildingName": "Trade Center",
  "officeNumber": "402"
}

label, governorateId, and neighborhoodId are required; the rest are optional (see Address fields).

Response: 201 Created

json
{
  "success": true,
  "data": {
    "address": { "_id": "665f...", "label": "Office", "isDefault": false /* ... */ },
    "addresses": [ /* all of the customer's addresses */ ]
  }
}

Update an address ​

PUT /integration/merchant/customer/:customerId/address/:addressId ​

Updates fields on an existing address. Send the same fields accepted at creation. Wasal verifies the customer belongs to your merchant before applying the change.

Set the default address ​

PUT /integration/merchant/customer/:customerId/address/:addressId/default ​

Marks the given address as the customer's default. When an order omits customerAddressId, the default address is used.

Delete an address ​

DELETE /integration/merchant/customer/:customerId/address/:addressId ​

Removes an address from the customer. The customer must have at least one remaining address before you can create new orders for them.

Delete a customer ​

DELETE /integration/merchant/customer/:customerId ​

Removes the customer from your merchant account. The customer record is soft-deleted and no longer accessible through your merchant key.

Customers with outstanding orders cannot be deleted. If the customer has one or more non-terminal orders (draft, pending, assigned, on_way_to_merchant, picked_up, in_transit, on_hold) under your merchant account, the request returns 409 CUSTOMER_HAS_ORDERS. Cancel or resolve the outstanding orders before deleting the customer.

Response: 200 OK

json
{ "success": true, "message": "Customer removed from merchant successfully" }

Errors ​

HTTPcodeMeaning
400VALIDATION_FAILEDOne or more fields invalid — see errors.
400MAX_ADDRESSES_EXCEEDEDCustomer already has 5 addresses.
404CUSTOMER_NOT_FOUNDCustomer does not exist or is not linked to your merchant.
404ADDRESS_NOT_FOUNDAddress does not exist on this customer.
409CUSTOMER_PHONE_DUPLICATEA customer with this phone already exists for your merchant.
409CUSTOMER_EMAIL_DUPLICATEA customer with this email already exists for your merchant.
409CUSTOMER_HAS_ORDERSCustomer has outstanding (non-terminal) orders and cannot be deleted.

See the full Error Reference.

Wasal Delivery Platform · Integration API v1.0.0