Shopopop API allows our partners to integrate their information system with ours.
Using Shopopop API, partners can create deliveries and follow their worflow.
All the endpoints are available in
The endpoints are the same in both environments, but the domains differ :
https://partners-api-preprod.shopopop.com
https://partners-api.shopopop.com
Each call must have the api_key
and partner_id
header.
Access information (api_key / partner_id) will be communicated by Shopopop.
Checks if a delivery is eligible according to the delivery time and travel distance.
partner_id | string |
api_key | string |
partner_id | string or null |
api_key | string or null |
required | object (PartnerDataRequest) |
{- "partner_id": "string",
- "api_key": "string",
- "data": {
- "retailer_code": "string",
- "drive": {
- "id": "string",
- "name": "string",
- "street": "string",
- "zip_code": "string",
- "city": "string",
- "telephone": "string",
- "additional_info": "string",
- "latitude": 0.1,
- "longitude": 0.1,
- "contact": { },
- "country": "string"
}, - "client": {
- "first_name": "string",
- "last_name": "string",
- "telephone": "string"
}, - "address": {
- "first_name": "string",
- "last_name": "string",
- "street": "string",
- "zip_code": "string",
- "city": "string",
- "country": "st",
- "other": "string",
- "telephone": "string",
- "telephone_other": "string",
- "type": 0,
- "digicode": "string",
- "floor": 0,
- "elevator": true,
- "comment": "string",
- "latitude": 0.1,
- "longitude": 0.1
}, - "delivery": {
- "reference": "string",
- "trip_id": "string",
- "amount": 999999.99,
- "delivery_date": "stringstri",
- "start_hour": "strin",
- "end_hour": "strin",
- "has_fresh_food": false,
- "quantity": 99999999999,
- "liquid_volume": 999999.99,
- "volume": 999999.99,
- "weight": 99999999999,
- "additional_infos": "string",
- "order_detail": [
- {
- "title": "string",
- "quantity": 99999999999,
- "weight": 999999.99,
- "volume": 99999999999
}
]
}
}
}
{- "error": true,
- "errorDesc": [
- "DELIVERY_DISTANCE_TOO_LONG"
]
}
You will notice this request parameters and the Check eligibility ones are the same.
⚠️ Calling this route immediately creates a valid delivery in our system and available for all shoppers.
If you need to validate a delivery without creating it, use the eligibility route.
Any updates on the created delivery will be done by our Customer Service.
To cancel a delivery, please refer to the next part of this document.
api_key and partner_id can be provided in the header (better) or in the body
partner_id | string |
api_key | string |
partner_id | string or null |
api_key | string or null |
required | object (PartnerDataRequest) |
creation_date required | string <date-time> |
message required | string (DuplicateDeliveryMessage) Value: "DUPLICATE" |
{- "partner_id": "string",
- "api_key": "string",
- "data": {
- "retailer_code": "string",
- "drive": {
- "id": "string",
- "name": "string",
- "street": "string",
- "zip_code": "string",
- "city": "string",
- "telephone": "string",
- "additional_info": "string",
- "latitude": 0.1,
- "longitude": 0.1,
- "contact": { },
- "country": "string"
}, - "client": {
- "first_name": "string",
- "last_name": "string",
- "telephone": "string"
}, - "address": {
- "first_name": "string",
- "last_name": "string",
- "street": "string",
- "zip_code": "string",
- "city": "string",
- "country": "st",
- "other": "string",
- "telephone": "string",
- "telephone_other": "string",
- "type": 0,
- "digicode": "string",
- "floor": 0,
- "elevator": true,
- "comment": "string",
- "latitude": 0.1,
- "longitude": 0.1
}, - "delivery": {
- "reference": "string",
- "trip_id": "string",
- "amount": 999999.99,
- "delivery_date": "stringstri",
- "start_hour": "strin",
- "end_hour": "strin",
- "has_fresh_food": false,
- "quantity": 99999999999,
- "liquid_volume": 999999.99,
- "volume": 999999.99,
- "weight": 99999999999,
- "additional_infos": "string",
- "order_detail": [
- {
- "title": "string",
- "quantity": 99999999999,
- "weight": 999999.99,
- "volume": 99999999999
}
]
}
}
}
{- "creation_date": "2019-08-24T14:15:22Z",
- "message": "DUPLICATE"
}
deliveryId required | string |
partner_id | string |
api_key | string |
delivery_start required | string start of the new delivery slot (format ISO full UTC), ex. 2023-09-03T14:30Z |
delivery_end required | string end of the new delivery slot (format ISO full UTC) |
{- "delivery_start": "string",
- "delivery_end": "string"
}
The errorDesc
field could have multiple values :
DELIVERY_DATE_TOO_CLOSE The date/time required for the delivery is too close (at least 2 hours before the beginning of the delivery timeslot).
DELIVERY_DISTANCE_TOO_LONG Distance between customer address and pickup location (drive) is too far.
WRONG_INTERVAL_BETWEEN_DELIVERY_DATES Delivery slot is too short.
{- "error": true,
- "errorDesc": [
- "DELIVERY_DISTANCE_TOO_LONG"
]
}
deliveryId required | string delivery's unique ID |
partner_id | string |
api_key | string |
partner_id | string or null |
api_key | string or null |
reason required | string reason of the cancellation |
{- "partner_id": "string",
- "api_key": "string",
- "reason": "string"
}
{- "error": true,
- "errorDesc": [
- "ERRAND_NOT_FOUND"
]
}
Return the latest status of a delivery.
deliveryId required | string delivery's unique ID |
partner_id | string |
api_key | string |
status required | string (DeliveryEventsStatus) |
event_date required | string <date-time> |
{- "status": "ADDED",
- "event_date": "2019-08-24T14:15:22Z"
}
Return all the events that occured on a delivery.
deliveryId required | string delivery's unique ID |
partner_id | string |
api_key | string |
status required | string (DeliveryEventsStatus) Enum: "ADDED" "ARRIVED_AT_CLIENT" "ARRIVED_AT_DRIVE" "BOOKED" "CANCELLED" "GOING_TO_CLIENT" "GOING_TO_DRIVE" "REMOVED" "SHOW_REFERENCE" "VALIDATED" "DELIVERY_INCIDENT" |
event_date required | string |
required | object (DeliveryEventAdditionalInfos) |
[- {
- "status": "ADDED",
- "event_date": "string",
- "additional_info": {
- "additional_reason": "string",
- "deliveryManFirstName": "string",
- "delivery_man_id": 0.1,
- "validation": "string",
- "is_in_area": true,
- "reason": "string",
- "partnerReference": "string",
- "new_start_date": "2019-08-24T14:15:22Z",
- "old_start_date": "2019-08-24T14:15:22Z",
- "new_start_date_utc": "2019-08-24T14:15:22Z",
- "old_start_date_utc": "2019-08-24T14:15:22Z",
- "new_end_date": "2019-08-24T14:15:22Z",
- "old_end_date": "2019-08-24T14:15:22Z",
- "new_end_date_utc": "2019-08-24T14:15:22Z",
- "old_end_date_utc": "2019-08-24T14:15:22Z",
- "reference": "string",
- "origin": "SHOPOPOP"
}
}
]
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 |
{- "event": "DELIVERY_ADDED",
- "date": "2019-08-24T14:15:22Z",
- "deliveryId": "string"
}