Rappi Connect Integration Guide

What is Rappi Connect?

Rappi Connect enables retailers to receive orders placed by customers on our platform. These orders can then be produced by retailers' staff using the tools and processes they already use to handle e-commerce orders.

Rappi Connect instantly integrates new orders into retailers' systems and manages the exchange of event notifications between Rappi and retailers throughout an order lifecycle. Receiving scheduled orders immediately allows retailers to maximize their picking staffs' productivity by utilizing valley (low demand) hours.

Important

Every new feature released by the Rappi Connect team improves the UX in integrated stores. As a Rappi Connect retailer, you must be committed to implementing these features within 8 weeks of release.

Integration Checklist

To successfully implement Rappi Connect:

  1. Understand our order lifecycle and events and how those events impact on user experience in the app.

  2. Understand which of your internal order fulfillment events should be mapped to ours.

  3. Understand the limitations of your ERP and/or picking system that might prevent the implementation of advanced order exception processes (and how you can overcome them).

  4. Develop webhooks to receive order-related events from Rappi.

  5. Notify Rappi of ALL order fulfillment events that occur in your operation.

The Order Lifecycle

The lifecycle of an order in Rappi Connect can be summarized in seven events. These order events must be exchanged between the retailer and Rappi in real-time.

Rappi Connect deals with two distinct events types:

  • internal event events sent from Rappi to the partner webhook
  • external event events that Rappi expects to receive from partners

Rappi must receive ALL events marked with the tag external event.

Base url for external events is /api/cpgops-integrations/

Examples:

  • /api/cpgops-integrations/orders/events
  • /api/cpgops-integrations/orders/{orderId}/cancel

All events sent from Rappi will be marked with the tag internal events.

  1. order_created internal event

The user places an order in one of our apps. Rappi will POST every new order to your webhook. Whenever you're unable to accept an order for whatever reason, your response must adhere to our integration error responses. This is extremely important to guarantee the integration reliability and makes triggering internal recovery flows possible.

On a successful response, Rappi expects to receive the external order id from retailers. Any event exchange (in both directions) will always use Rappi's order_id (internal order id). Make sure to maintain a mapping of those identifiers to properly publish and ingest order events.

POST /orders

{
"total_value": 35.449903,
"order_id": "12345",
"retail_store_id": "217",
"delivery": {
"delivery_time": "2021-04-23T20:00:00.000Z",
"departure_time": "2021-04-23T19:42:00.000Z"
},
"client": {
"phone": "99999999999",
"first_name": "Renato",
"last_name": "Silva",
"identification": "77238991659",
"email": "teste@gmail.com"
},
"products": [
{
"value": 12.990334,
"id": "296145320",
"value_without_discount": 14.99,
"units": 1,
"unit_value": 12.990334,
"retail_id": "4370",
"units_type": "g",
"unit_value_without_discount": 14.99,
"quantity": 1
},
{
"value": 7.4895689999999995,
"id": "296145319",
"value_without_discount": 8.99,
"units": 1,
"unit_value": 7.4895689999999995,
"retail_id": "8861",
"units_type": "g",
"unit_value_without_discount": 8.99,
"quantity": 1
},
{
"value": 14.97,
"id": "296145321",
"value_without_discount": 14.97,
"units": 3,
"unit_value": 4.99,
"retail_id": "17887",
"units_type": "g",
"unit_value_without_discount": 4.99,
"quantity": 3
}
],
"address": {
"city": "Guarulhos",
"neighborhood": "Aeroporto",
"region": "SP",
"number": 3,
"street_address": "Rod. Hélio Smidt",
"zip_code": "07190-100"
},
"combos": [
{
"combo_id": "12345",
"units": 1,
"products": [
{
"retail_id": ...,
"units": 1, // EACH UNIT IN THE COMBO,
"units_type": ..., // Und
},
{
"retail_id": ...,
"units": 2, // UNITS IN THE COMBO,
"units_type": ...,
}
]
}
]
"order_preference": "update_products"
}

Response

HTTP status code 201

{
"retail_order_id": "" // order id generated in the retailer side
}
  1. order_integrated external event

You successfully ingested the order into your picking system or ERP, and it's now available to be assigned to a picker.

{
"event": "order_integrated",
"timestamp": "2010-01-01T12:00:00Z",
"payload": {
"order_id": "12345"
}
}
  1. released_to_picker external event

Your picker starts the shopping mission for one or more orders. Picking must be completed at least 30 minutes before our delivery promise to the user. The delivery promise is found in each new order pushed by Rappi to your webhook; under the request-body property delivery.delivery_time.

{
"event": "released_to_picker",
"timestamp": "2010-01-01T12:00:00Z",
"payload": {
"order_id": "12345"
}
}
  1. invoice_created external event

Your picker has completed the shopping mission, i.e., items have been packaged and invoiced. This event tells Rappi the order is ready to be picked up by a courier.

Rappi notifies couriers that a new order is available for delivery when the order has been invoiced by the retailer.

{
"event": "invoice_created",
"timestamp": "2010-01-01T12:00:00Z",
"payload": {
"order_id": "12345",
"invoice": "", // Optional eg(url of the invoice)
"total": 100.9, // Optional (invoice total price)
"preferred_transport": "" // ('bicycle', 'motorbike' or 'car') defaults to 'motorbike'
}
}
  1. courier_assigned internal event

When a courier accepts a delivery, Rappi will PUT the courier information to your delivery information webhook.

PUT /orders/{orderId}/delivery

{
"order_id": 0,
"courier_name": "string",
"courier_id": 0,
"identification_id": "string",
"vehicle_type": "string",
"delivery_time": "2021-04-07T18:59:02.867Z",
"departure_time": "2021-04-07T18:59:02.867Z"
}

Response HTTP status code 204

  1. order_delivered internal event

Rappi will notify retailers every successful delivery at their webhook.

POST /orders/{orderId}/finish

Response HTTP status code 204

note

It is vital to understand the entire lifecycle to:

  • guarantee the user experience in integrated stores
  • monitor, and respond to, operational issues
warning

Failing to regularly notify Rappi of external order events might trigger our bots to temporarily pause your stores, leading to a loss in Gross merchandise value (GMV) and frustrating users that look for those stores on our platform.

Integration Error Responses

Integration error responses are a subtopic of the order created event. Whenever retailers are unable to accept a new order, the reason for its rejection must be properly communicated with 4XX HTTP error code and details provided in the reason-specific schema.

Integration errors are organized around eight groups.

c1c2c3
RangeGroupDescription
10-19service dependenciesinternal use only
20-29retailers outages or errorsinternal use only
30-39basic order detailsMissing basic order information
40-49product informationInconsistent product information (catalog, stock, price)
50-59user informationMissing or inconsistent customer information
60-69user address informationIncomplete or inconsistent customer address
70-79delivery informationMissing or inconsistent delivery/departure time information
80-99reserved
90-99reserved
0uncategorizedOrder is unacceptable due to an uncategorized issue

The response for most integration errors will follow the basicSchema, which simply provides the identified error code as provided in the table below.

Table of error codes applicable to synchronous order validations:

c1c2c3c4
TopicError CodeHTTP CodeResponse
order-id-missing30400Basic error schema
order-id-duplicated31409Error 31 Schema
store-not-found32400Basic error schema
total-value-inconsistent33400Basic error schema
products-not-found40400Error 40 Schema
products-stock-out41400Error 41 Schema
products-price-difference42400Error 42 Schema
user-first-name50400Basic error schema
user-last-name51400Basic error schema
user-identification52400Basic error schema
user-email53400Basic error schema
user-phone-number54400Basic error schema
address-street-address60400Basic error schema
address-number61400Basic error schema
address-neighborhood62400Basic error schema
address-city63400Basic error schema
address-state64400Basic error schema
address-zip-code65400Basic error schema
delivery-time70400Basic error schema
departure-time71400Basic error schema
uncategorized0400Uncategorized schema

Error Responses

Basic error schema

  • order-id-missing    order-id-duplicated    store-not-found    total-value-inconsistent    user-first-name    user-last-name    user-identification    user-email    user-phone-number    address-street-address    address-number    address-neighborhood    address-city    address-state    address-zip-code    delivery-time    departure-time
{
"error_code": XX // integer error code
}

Error 31 Schema

  • order-id-duplicated
{
"error_code": 31,
"payload": {
"retail_order_id": "1234",
"created_at": "2010-01-01T12:00:00Z"
}
}

Error 40 Schema

  • products-not-found
{
"error_code": 40,
"details": {
"products": ["1234", "9876"]
}
}

Error 41 Schema

  • products-stock-out
{
"error_code": 41,
"details": {
"products": [{
"retail_id": "9876",
"available": 6
}]
}
}

Error 42 Schema

  • products-price-difference
{
"error_code": 42,
"details": {
"difference_threshold": 10,
"products": [{
"retail_id": "9876",
"price_difference": 0.85
}]
}
}

Uncategorized schema

  • uncategorized
{
"error_code": 0,
"message": "" ,
}

Exceptions

Exceptions are any event that prevents orders from following their regular path. Causes may include:

  • one of the SKUs in the order is no longer available;
  • the retailer is temporarily unable to invoice orders; e.g., the power is down in a store or warehouse, and fulfillment is paused until power is restored. This would result in a cancellation.

Whatever the reason, exceptions must be handled properly. Note that a single out-of-stock SKU is not a valid exception. We're not supposed to cancel an entire order because one single SKU isn't available; the user must be informed of the exact reason that led to a cancellation.

Rappi expects any exception event to be posted to the order notification endpoint endpoint. As previously mentioned, this endpoint receives all external events from retailers. Therefore, review the respective schemas that apply.

Proper handling of exception events are of utmost importance for:

  • notifying customers they'll be receiving a partial order,
  • adjusting the total amount charged for the order, and
  • preventing stockout SKUs from showing in the app.

Partial Orders

A partial order is an exception. Sending a partial order to a customer is an outcome triggered by one or more SKUs in the order being unavailable at the time of integration or during the picker shopping mission. EVERY Rappi Connect retailer must have the capability to process partial orders. This prevents cancellation of an entire order when a stock out is identified.

When reducing product units or entirely removing a product from an order, Rappi expects these events to be posted to the order event notification endpoint.

The schema that applies depends on whether you are reducing units or removing the product from the order:

Important

When using remove_product_units or remove_product you must send only one product per request. If you have to remove more than one product perform additional requests.

  1. remove_product_units external event

POST /orders/events

You must notify this the order endpoint whenever a reduction event occurs, i.e., if you reduce the number of units of any product in the order due to not having enough inventory at hand.

{
"event": "remove_product_units",
"timestamp": "2010-01-01T12:00:00Z",
"payload": {
"order_id": "12345",
"product_units_to_remove": {
"123": 1, // product_id: quantity to remove
"1234": 2
}
}
}
  1. remove_product external event

POST /orders/events

Alternatively, in the event of not having any stock for an SKU at hand, you must notify this endpoint to remove that product entirely from the order

{
"event": "remove_product",
"timestamp": "2010-01-01T12:00:00Z",
"payload": {
"order_id": "12345",
"removed_product_id": "" // product_id
}
}
  1. reschedule_order external event

POST /orders/events

If there is any operational problem in the store, we allow the retailer to reschedule the order to avoid cancellation. When this happens, the retailer will make a request to the following endpoint:

{
"event": "remove_product",
"timestamp": "2010-01-01T12:00:00Z",
"payload": {
"order_id": "12345",
"schedule_at": "2010-01-01T12:00:00Z"
}
}

Customer-Driven Modifications

A customer can issue modifications after the order is placed for partners. The allowed modifications are changing the delivery slot, adding products and removing products.

How does it work?

When there is an order modification, Rappi will send a PUT with the updated order to the partner with the respective modification reason and the same order_id in integer format. The modifications are the following:

  1. schedule_modification internal event

In this case the order object can be exactly the same an the one created before. Nevertheless, the retailer must calculate the differences and make them reflect in their picking system.

{
"modification": "schedule_modification",
"order": ...
}
  1. products_updated internal event

The order object contains the updated order. The retailer must calculate the differences and make them reflect in their picking system.

{
"modification": "products_updated",
"order": ...
}

Medical Prescriptions

Rappi Connect has some partners integrated from pharma who sell medications that need prescriptions. For the generic order integration, relevant information should be added to the products property in the order.

The medical_prescriptions property should be added to the order creation request to the retailer.

{
...
"products": [
{
"id": 12323,
"retail_id": "12323123",
"quantity": 1 ,
"units": 4,
"units_type": null,
"value": 12323,
"medical_prescriptions": [
{
"type": "prescription_file",
"value": ""
},
{
"type": "prescription_code",
"value": ""
},
{
"type": "prescription_provider",
"value": ""
},
]}
]
}

When Orders are Cancelled

A cancellation is an exception. A cancellation may happen at either end; an order may either be cancelled by:

  • the customer in our apps, or
  • a retailer when unable to fulfill that order.

Whatever the origin of a cancellation, the other party must be promptly notified.

Whenever an order is cancelled by a customer, Rappi will POST an a notification to your cancellation webhook:

POST /orders/{orderId}/cancel

When orders are cancelled by a retailer, Rappi must be notified and informed of the reason that led to order cancellation. Notifications must be posted to the endpoint. Please review the cancellation reasons table next and the schema associated with each of them.

c1c2c3
TopicCancel Reason CodeResponse
store-not-found32Basic cancel schema
products-not-found40Cancel reason 40 schema
products-stock-out41Cancel reason 41 schema
products-price-difference42Cancel reason 42 schema
products-discontinued43Cancel reason 43 schema
uncategorized0Cancel uncategorized schema

Cancel Payloads

Basic cancel schema

  • store-not-found
{
"event": "order_cancelled",
"timestamp": "2021-06-18T16:42:03Z",
"payload": {
"triggered_from": "string",
"order_id": "1234",
"cancel_reason_code": 32
}
}

Cancel reason 40 schema

  • products-not-found
{
"event": "order_cancelled",
"timestamp": "2021-06-18T16:42:03Z",
"payload": {
"triggered_from": "string",
"order_id": "1234",
"cancel_reason_code": 40,
"details": {
"products": [ "1234", "1235" ]
}
}
}

Cancel reason 41 schema

  • products-stock-out
{
"event": "order_cancelled",
"timestamp": "2021-06-18T16:42:03Z",
"payload": {
"triggered_from": "string",
"order_id": "1234",
"cancel_reason_code": 41,
"details": {
"products": [{
"retail_id": "9876",
"available": 6
}]
}
}
}

Cancel reason 42 schema

  • products-price-difference
{
"event": "order_cancelled",
"timestamp": "2021-06-18T16:42:03Z",
"payload": {
"triggered_from": "string",
"order_id": "1234",
"cancel_reason_code": 42,
"details": {
"difference_threshold": 10,
"products": [{
"retail_id": "9876",
"price_difference": 0.85
}]
}
}
}

Cancel reason 43 schema

  • products-discontinued
{
"event": "order_cancelled",
"timestamp": "2021-06-18T16:42:03Z",
"payload": {
"triggered_from": "string",
"order_id": "1234",
"cancel_reason_code": 43,
"details": {
"retail_ids": ["12345", "123456"]
}
}
}

Cancel uncategorized schema

  • uncategorized
{
"event": "order_cancelled",
"timestamp": "2021-06-18T16:42:03Z",
"payload": {
"triggered_from": "string",
"order_id": "1234",
}
}

Handshake

Handshake is the event that guarantees that the order has been delivered to the right RT.

The handshake feature allows a retailer to have more security and reduce the risk of fraud at the time of delivering an order to an RT to carry out the address of the same.

Requesting the handshake

To start the handshake flow, the retailer must make the following request:

POST /v1/orders/{orderId}/handshake

Successfull response

{
"codes": [
"033903",
"286928",
"788069"
],
"expires_at": "2021-06-18T16:42:03Z"
}

Among the codes returned to the retailer in the codes property, only one of them is valid and it will arrive at the RT of the order through its mobile application. The generated codes will expire after the date indicated in the expires_at field. Once the codes expire, the handshake can be requested again, at which time new codes will be generated with a different expiration date.

What are the possible outputs when requesting the handshake

Order not found

If the order with which you want to start the handshake process is not found in the Rappi system, the following response will be obtained with status code 404:

{
"type": "order_not_found",
"message": "The order given was not found in the system"
}
Order doesn't belong to the retailer

If the order does not belong to the retailer making the request, the following response with status 403 is obtained:

{
"type": "forbidden_order_operation",
"message": "The order operation is not allowed"
}
RT not assigned

To start the handshake process it is necessary that an RT is assigned to the order, otherwise the following error with status 400 is obtained:

{
"type": "no_courier_assigned",
"message": "No courier is assigned to the order yet"
}
Handshake already started

If the request to generate codes and start the handshake process is made again when there are still unexpired codes, the following error with status code 400 is obtained:

{
"type": "handshake_already_started",
"message": "The handshake process was already started for the order"
}

Validating the handshake

To validate if the RT to which the order is to be delivered is the one that is truly assigned to it, it must choose the correct one from the possible codes. To validate if a code given by an RT is correct, the retailer must make the following request:

POST /v1/orders/{orderId}/handshake/validate

Request

{
"code": "788069"
}

If the code sent in the validation request is correct, a response will be obtained with status code 204 and without any body. When this happens, the retailer can deliver the order to RT.

If the selected code is incorrect, an error with status code 400 and content like the following is obtained:

{
"type": "invalid_handshake_code",
"message": "An invalid handshake code was provided",
"details": {
"codes": [
"494805",
"002786",
"883029"
],
"expires_at": "2021-06-23T15:23:49Z",
"retries_left": 3
}
}
When validation doesn't match

By making a mistake with the validation of the handshake process, the retailer will receive a new set of possible codes and the correct RT will again receive the valid code for their application. It should be noted that there is a limit of 4 validation attempts per order. It should also be noted that the response for the fourth failed validation attempt is slightly different, as it does not include a new set of codes or an expiration date:

{
"type": "invalid_handshake_code",
"message": "An invalid handshake code was provided",
"details": {
"retries_left": 0
}
}
When retries are exhausted

If all the handshake validation attempts for an order are exhausted, the response that will be obtained from both the endpoint to request handshake, and from the endpoint to validate a handshake code will be an error with status code 400 with the following content:

{
"type": "no_validation_retries_left",
"message": "The limit of validation attempts was reached"
}
When handshake request doens't exist

It should also be considered that if it is a question of validating a code without having made a handshake request before, or if the codes have expired, the following error response with status code 400 is obtained:

{
"type": "handshake_request_required",
"message": "The request to start the handshake process is required first"
}

In case of ending the number of tries of the handshake the order doesn't have to be cancelled but, on the other hand, is not possible to start the process again from scratch. If that happens, the RT will have to contact Rappi's support, explain the situation and have the order released by them.