This is the documentation of Shopopop API.
Shopopop API allows our partners to integrate their information system with ours.
Using Shopopop API, partners can create deliveries and track their worflow.
All calls must use HTTPS.
All the endpoints are available in
The endpoints are the same in both environments, but the domains differ :
https://partners-api.sandbox.shopopop.com/v2
https://partners-api.shopopop.com/v2
To authenticate API requests, use Basic Authentication by including the Authorization
header with each call otherwise a 401 Unauthorized
error will be returned.
Authorization
header should be in the format:
Authorization: Basic <base64-encoded-credentials>
<base64-encoded-credentials>
is the base64 encoding of the partner_id:partner_key
.Authorization
header might look like this:
Authorization: Basic cGFydG5lcl9pZDpwYXJ0bmVyX2tleQ==
(e.g. in decoded form it would be Authorization: Basic partner_id:partner_key
)Checks if a delivery is eligible according to the delivery time and travel distance.
This endpoint use the same payload as the delivery creation endpoint. If the delivery is not eligible, the response is an error with the reason of the refusal. If it is eligible, the response is a success; the delivery is not created.
retailer | string or null <= 255 characters Code of the retailer If you can create deliveries for several retailers, this code indicate for which retailer this delivery should be done. This value is provided by Shopopop. |
required | object (PartnerPickupRequest) |
required | object (PartnerDropoffRequest) Information about the drop-off location. If the latitude and longitude are provided these are use to define the drop-off location. The other address information are still displayed to the deliverer as additional information. If latitude and longitude are not given, "street", "zip_code", "city" and "country" are used to geolocate the address. |
required | object (PartnerDeliveryRequest) |
{- "retailer": "string",
- "pickup": {
- "id": "A-1234",
- "name": "My Store"
}, - "dropoff": {
- "dropoff_start": "2023-09-03T14:30Z",
- "dropoff_end": "2023-09-03T16:30Z",
- "contact": {
- "first_name": "string",
- "last_name": "string",
- "email": "string",
- "phone": "+1234567890"
}, - "street": "20 rue Jules Verne",
- "zip_code": 44000,
- "city": "Nantes",
- "country": "FR",
- "phone_other": "+1234567890",
- "location_name": "Company Name",
- "type": "COMPANY",
- "floor": 0,
- "elevator": true,
- "comment": "At the end of the street, house with a red door.",
- "latitude": 47.206377,
- "longitude": -1.564499
}, - "delivery": {
- "id": "string",
- "order": {
- "reference": "ORD_000001",
- "amount": 140.59,
- "number_of_items": 72,
- "frozen_food": false,
- "alcohol": false,
- "order_detail": [
- {
- "title": "string",
- "quantity": 1
}
]
}, - "content_size": {
- "category": "XS",
- "weight": 42.5,
- "volume": 65.3,
- "dimensions": {
- "length": 60,
- "height": 30,
- "depth": 40
}, - "heavy_item": true,
- "bulky_item": true,
- "size_for_florists": [
- {
- "type": "SINGLE_BOUQUET",
- "quantity": 1
}
]
}, - "special_event": "GIFT",
- "minimal_transport_mode": "TRANSPORT_1",
- "additional_infos": "string"
}
}
{- "error": {
- "code": "invalid_format",
- "message": "Invalid format",
- "details": {
- "delivery.id": {
- "message": "required"
}
}
}
}
Note that the parameters are the same as the Check eligibility parameters.
Calling this route creates a valid delivery in our system that a deliverer can book immediately.
If you need to validate a delivery without creating it, use the eligibility route.
retailer | string or null <= 255 characters Code of the retailer If you can create deliveries for several retailers, this code indicate for which retailer this delivery should be done. This value is provided by Shopopop. |
required | object (PartnerPickupRequest) |
required | object (PartnerDropoffRequest) Information about the drop-off location. If the latitude and longitude are provided these are use to define the drop-off location. The other address information are still displayed to the deliverer as additional information. If latitude and longitude are not given, "street", "zip_code", "city" and "country" are used to geolocate the address. |
required | object (PartnerDeliveryRequest) |
creation_date required | string <date-time> |
message required | string Value: "DUPLICATE" |
object or null <date-time> |
{- "retailer": "string",
- "pickup": {
- "id": "A-1234",
- "name": "My Store"
}, - "dropoff": {
- "dropoff_start": "2023-09-03T14:30Z",
- "dropoff_end": "2023-09-03T16:30Z",
- "contact": {
- "first_name": "string",
- "last_name": "string",
- "email": "string",
- "phone": "+1234567890"
}, - "street": "20 rue Jules Verne",
- "zip_code": 44000,
- "city": "Nantes",
- "country": "FR",
- "phone_other": "+1234567890",
- "location_name": "Company Name",
- "type": "COMPANY",
- "floor": 0,
- "elevator": true,
- "comment": "At the end of the street, house with a red door.",
- "latitude": 47.206377,
- "longitude": -1.564499
}, - "delivery": {
- "id": "string",
- "order": {
- "reference": "ORD_000001",
- "amount": 140.59,
- "number_of_items": 72,
- "frozen_food": false,
- "alcohol": false,
- "order_detail": [
- {
- "title": "string",
- "quantity": 1
}
]
}, - "content_size": {
- "category": "XS",
- "weight": 42.5,
- "volume": 65.3,
- "dimensions": {
- "length": 60,
- "height": 30,
- "depth": 40
}, - "heavy_item": true,
- "bulky_item": true,
- "size_for_florists": [
- {
- "type": "SINGLE_BOUQUET",
- "quantity": 1
}
]
}, - "special_event": "GIFT",
- "minimal_transport_mode": "TRANSPORT_1",
- "additional_infos": "string"
}
}
{- "creation_date": "2019-08-24T14:15:22Z",
- "message": "DUPLICATE",
- "last_event": {
- "event": "DELIVERY_ADDED",
- "date": "string"
}
}
Return delivery information
deliveryId required | string Unique ID of the delivery. This is the ID provided in the creation. |
delivery_id | string [ 1 .. 60 ] characters Unique ID of the delivery |
status | string Enum: "AVAILABLE" "RESERVED" "TO_PICKUP_POINT" "AT_PICKUP_POINT" "PICKUP_VALIDATED" "TO_RECIPIENT" "AT_RECIPIENT" "VALIDATED" "INCIDENT" "DELETED" Status of the delivery |
object | |
object | |
object |
{- "delivery_id": "string",
- "status": "AVAILABLE",
- "pickup": {
- "id": "string",
- "name": "string",
- "pickup_start": "2023-09-03T14:30Z",
- "pickup_end": "2023-09-03T15:30Z"
}, - "dropoff": {
- "dropoff_start": "2023-09-03T15:00Z",
- "dropoff_end": "2023-09-03T16:00Z",
- "contact": {
- "first_name": "string",
- "last_name": "string",
- "email": "string",
- "phone": "+1234567890"
}
}, - "delivery": {
- "order": {
- "reference": "ORD_000001",
- "amount": 57.69
}, - "estimated_distance": 5560,
- "content_size": {
- "category": "XS"
}, - "size_for_florists": [
- {
- "type": "SINGLE_BOUQUET",
- "quantity": 1
}
], - "additional_infos": "string"
}
}
deliveryId required | string Unique ID of the delivery. This is the ID provided in the creation. |
reason | string or null [ 0 .. 255 ] characters Reason of the cancellation |
{- "reason": "Cancelled by the recipient"
}
{- "error": {
- "code": "invalid_format",
- "message": "Invalid format",
- "details": {
- "delivery.id": {
- "message": "required"
}
}
}
}
deliveryId required | string Unique ID of the delivery. This is the ID provided in the creation. |
dropoff_start required | string Start of the new delivery slot (format ISO full UTC) |
dropoff_end required | string End of the new delivery slot (format ISO full UTC) |
{- "dropoff_start": "2023-09-03T14:30Z",
- "dropoff_end": "2023-09-03T15:30Z"
}
{- "error": {
- "code": "invalid_format",
- "message": "Invalid format",
- "details": {
- "delivery_start": {
- "message": "bad format"
}
}
}
}
Webhook used to send events occuring on a delivery. Those events can be a status change, or a delivery slot modification for example. To use webhooks, you have to give us the URL to use to send you this information.
To ensure the integrity and authenticity of the webhook events received from our API, it's crucial to verify the signature sent with each request. This guide provides instructions on validating webhook payloads.
Each webhook request includes a signature in the X-Shopopop-Signature-256
header. Use this signature to verify the payload's integrity by computing the expected hash and comparing it to the received hash.
Ask our team to generate a secret token using a high-entropy string, which will be used to compute a hash signature for each payload. When creating a new webhook, input this token into the appropriate field in your configuration settings.
Store the token in a secure location accessible by your server's environment. Avoid hardcoding it directly into your codebase or version control systems.
Given this payload:
{
"date": "2023-12-19T15:00:00.000Z",
"deliveryId": "PARTNERREF12345",
"event": "DELIVERY_ADDED"
}
With this signatureKey : 12345-abcde-£.?./+
Header will be prefixed with sha256=
so an example header given the signatureKey
and payload
above :
X-Shopopop-Signature-256 : sha256=0dd4e829b49855c4238ca56b9cc241aee35106274124ca656ace52b277cc07dc
const crypto = require('crypto');
function verifySignature(payload, receivedSig, secret) {
const hash = crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const expectedSig = `sha256=${hash}`;
return crypto.timingSafeEqual(Buffer.from(expectedSig), Buffer.from(receivedSig));
}
// Example usage
const payload = JSON.stringify(request.body);
const receivedSig = request.headers['x-shopopop-signature-256'];
const secret = process.env.SIGNATURE_KEY;
if (!verifySignature(payload, receivedSig, secret)) {
console.error('Invalid signature');
}
import hmac
import hashlib
def verify_signature(payload, received_sig, secret):
hash = hmac.new(secret.encode(), msg=payload.encode(), digestmod=hashlib.sha256).hexdigest()
expected_sig = f'sha256={hash}'
return hmac.compare_digest(expected_sig, received_sig)
# Example usage
payload = request.get_data(as_text=True)
received_sig = request.headers.get('X-Shopopop-Signature-256')
secret = os.getenv('SIGNATURE_KEY')
if not verify_signature(payload, received_sig, secret):
print('Invalid signature')
If the signature verification fails:
X-Shopopop-Signature-256
header is being used.By following these guidelines, you can secure your webhook interactions against unauthorized and tampered requests.
If webhooks calls are rejected by the partner with one of these HTTP errors : 408/429/500/502/503/504, then a new call is tried 1 min later.
If we still get an error, 2 other tries are done after 15 min and 60 min.
X-Shopopop-Signature-256 required | string The signature of the request body, computed using the HMAC-SHA256 algorithm, the secret token as key, and the request body as value, then encoded to hexadecimal. |
Information about the event
event required | string (WebhookEvents) |
date required | string <date-time> |
deliveryId required | string |
required | object |
{- "event": "DELIVERY_ADDED",
- "date": "2019-08-24T14:15:22Z",
- "deliveryId": "string",
- "data": {
- "distance": 7522,
- "pickupStart": "2023-09-03T14:30Z",
- "pickupEnd": "2023-09-03T15:30Z"
}
}