Self-Onboarding

API Reference — Stores API Reference — Webhooks

Self-onboarding allows POS integrators to associate new stores to their integration autonomously, without requiring manual intervention per store. Once a TAM creates the Integration and clientId in Auth0, your system can provision and deprovision stores programmatically at any scale.

Prerequisites

Before using self-onboarding, a Technical Account Manager (TAM) must perform a one-time setup:

  • Create the Integration entity in the Rappi backend.
  • Create the clientId in Auth0 associated with your integration.

After this setup, all provisioning operations are fully self-service.

Authentication

Self-onboarding uses a two-token model. Your application needs to obtain two independent tokens before calling any self-onboarding endpoint:

Integrator token (your credentials)

Obtain a machine-to-machine (M2M) token using your client_id and client_secret. For details on how to obtain this token, see the Authentication guide.

This token is long-lived (configurable TTL). Cache it and only refresh when it expires.

Merchant token (your merchant's credentials)

To access a merchant's stores, your application must redirect the merchant through Rappi's OAuth2 Authorization Code + PKCE flow on Portal Partners. This authorizes your integration to act on the merchant's behalf.

EnvironmentAuthorization URLToken URL
Productionhttps://login.partners.rappi.com/authorizehttps://login.partners.rappi.com/oauth/token
Developmenthttps://login.partners.dev.rappi.com/authorizehttps://login.partners.dev.rappi.com/oauth/token

ℹ️ The examples below use the Production environment. Replace the URLs with the Development endpoints shown above when testing.

⚠️ The client_id and your redirect_uri must be registered with the Rappi integrations team. Contact your TAM.

Step 1 — Generate a PKCE code challenge

Before redirecting, your backend must generate a PKCE pair:

  1. Generate a cryptographically random code_verifier (43–128 URL-safe characters)
  2. Compute code_challenge = BASE64URL(SHA256(code_verifier))

Step 2 — Redirect the merchant

Build the authorization URL and redirect the merchant's browser:

GET https://login.partners.rappi.com/authorize ?client_id=YOUR_CLIENT_ID &redirect_uri=YOUR_REDIRECT_URI &response_type=code &scope=openid profile email &code_challenge=YOUR_CODE_CHALLENGE &code_challenge_method=S256 &state=YOUR_RANDOM_STATE

⚠️ Always validate that the state value in the redirect matches what you sent — this prevents CSRF attacks.

Step 3 — Merchant authenticates

The merchant logs in with their Rappi Portal Partners credentials. Once done, Portal Partners redirects back to your redirect_uri with an authorization code:

GET YOUR_REDIRECT_URI?code=AUTHORIZATION_CODE&state=YOUR_RANDOM_STATE

Step 4 — Exchange the code for a merchant JWT

Your backend exchanges the authorization code for a merchant JWT:

POST https://login.partners.rappi.com/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=AUTHORIZATION_CODE &client_id=YOUR_CLIENT_ID &code_verifier=YOUR_CODE_VERIFIER &redirect_uri=YOUR_REDIRECT_URI

ℹ️ No client_secret is required — this is a public OAuth2 client using PKCE.

The response includes the merchant JWT. This token contains the merchant's email in its claims.

Step 5 — Use both tokens on every request

HeaderValuePurpose
AuthorizationBearer <integrator JWT>Identifies your integration
Authorization-PartnersBearer <merchant JWT>Grants access to the merchant's stores

Step-by-Step Flow

Step 1 — Configure the webhook (once)

Register a webhook URL for your integration so Rappi can notify you when a provisioning operation completes. This is a one-time setup per integration.

Use the POST /clients/{clientId}/webhooks endpoint:

POST /clients/{clientId}/webhooks

Request body:

{ "event": "STORE_PROVISIONING_STATUS", "url": "https://your-endpoint.com/rappi/events", "secret": "your-hmac-secret" }

The event field specifies which event to configure (currently only STORE_PROVISIONING_STATUS is supported). The secret field is optional. If provided, Rappi will sign each webhook payload with HMAC-SHA256 and include the signature in the Rappi-Signature header so you can verify the request origin. If omitted, no signature will be sent — provide your own secret and store it securely if you need to verify webhook authenticity.

A 201 Created response confirms the webhook is configured (or 200 OK if updating an existing configuration). From this point on, all provisioning and deprovisioning results for your integration will be delivered to this URL.

Step 2 — Retrieve Stores

Before provisioning, verify which stores are already integrated and which are not. This avoids duplicate provisioning requests.

Use the GET /v2/stores/integration-status endpoint:

GET /v2/stores/integration-status

Headers:

HeaderValue
AuthorizationBearer <integrator JWT> (M2M, Auth0)
Authorization-PartnersBearer <merchant JWT> (OAuth2, Auth0)

The store IDs are fetched automatically from the merchant's JWT email — no request body is needed.

Response:

{ "stores": [ { "storeId": "1", "name": "Your Brand Main", "brand": "YourBrand", "integrated": true, "integrationId": "your-integration-id", "children": [ { "storeId": "3", "name": "Child Store 3", "brand": "YourBrand", "integrated": true, "integrationId": "your-integration-id" }, { "storeId": "4", "name": "Child Store 4", "brand": "YourBrand", "integrated": false } ] }, { "storeId": "2", "name": "Your Brand Secondary", "brand": "YourBrand", "integrated": true, "integrationId": "your-integration-id", "children": [] }, { "storeId": "10", "name": "Sertester1", "brand": "YourBrand", "integrated": false, "children": [ { "storeId": "20", "name": "Sertester1 Child 1", "brand": "YourBrand", "integrated": false }, { "storeId": "21", "name": "Sertester1 Child 2", "brand": "YourBrand", "integrated": false } ] }, { "storeId": "11", "name": "FIFOUno", "brand": "YourBrand", "integrated": false, "children": [] } ] }

Proceed to provision only the stores where integrated is false. Each entry includes storeId, name, and brand to help you identify the store. The children array reflects the hierarchy — each child also carries an integrated flag and, when integrated, its integrationId.

Step 3 — Activation

Submit a provisioning request for the stores that are not yet integrated. This operation is asynchronous — the API returns 202 Accepted immediately and the result is delivered via webhook. You can provision up to 20 stores per request.

Use the POST /v2/stores/provisioning endpoint:

POST /v2/stores/provisioning

Headers:

HeaderValue
AuthorizationBearer <integrator JWT> (M2M, Auth0)
Authorization-PartnersBearer <merchant JWT> (OAuth2, Auth0)

Request body:

{ "stores": [ { "storeId": "10", "name": "My Store", "integrationId": "your-integration-id" }, { "storeId": "11", "name": "My Other Store", "integrationId": "your-integration-id" } ] }

All three fields — storeId, name, and integrationId — are required per store item. Stores missing any of these fields will be rejected. The brand is resolved automatically from Portal Partners based on the merchant's credentials.

The following fields are optional and control integration behavior. If not provided, the defaults shown below are applied:

FieldTypeDefaultDescription
pingActivebooleanfalseEnable ping events
getMenuActivebooleanfalseEnable get-menu events
cancellationEventsbooleanfalseEnable cancellation events
statusstringINACTIVEInitial store status. Use ACTIVE to activate immediately on provisioning

Response: 202 Accepted

{ "batchId": "550e8400-e29b-41d4-a716-446655440000", "accepted": [ { "storeId": "10", "integrationId": "your-integration-id" } ], "rejected": [ { "storeId": "11", "reason": "not_owned" } ] }

Possible rejected[].reason values:

ReasonMeaning
not_ownedThe store does not belong to the authenticated merchant.
missing_nameThe name field is missing or blank.
invalid_integration_idThe integrationId field is missing or blank.

If all stores are rejected (none owned by the merchant), the API returns 422 Unprocessable Entity.

Step 4 — Receive the webhook

When the provisioning operation completes, Rappi sends a STORE_PROVISIONING_STATUS event to the URL configured in step 1.

See STORE_PROVISIONING_STATUS for the full payload reference.

Example payload:

{ "batchId": "550e8400-e29b-41d4-a716-446655440000", "integrationId": "your-integration-id", "operation": "PROVISION", "results": [ { "storeId": "10", "integrationId": "your-integration-id", "brand": "YourBrand", "status": "ACTIVE", "httpCode": 201 }, { "storeId": "11", "integrationId": "your-integration-id", "brand": "YourBrand", "status": "FAILED", "errorMessage": "Store already exists", "httpCode": 409 } ], "timestamp": "2026-04-21T10:00:00Z" }

Possible results[].status values:

StatusMeaning
ACTIVEProvisioning completed successfully. The store is now integrated.
INACTIVEDeprovisioning completed successfully.
FAILEDThe operation failed. Check errorMessage and httpCode for details.

results[].httpCode is the HTTP status code of the underlying operation (e.g. 201 for success, 409 for conflict, 204 for deprovisioning).

operation is either PROVISION or DEPROVISION.

Step 5 — Deactivating (optional)

To remove stores from your integration, use the POST /v2/stores/deprovisioning endpoint. Like provisioning, this is asynchronous — the result is delivered via the same STORE_PROVISIONING_STATUS webhook with operation: DEPROVISION and status: INACTIVE per store.

POST /v2/stores/deprovisioning

Headers:

HeaderValue
AuthorizationBearer <integrator JWT> (M2M, Auth0)
Authorization-PartnersBearer <merchant JWT> (OAuth2, Auth0)

Request body:

{ "stores": [ { "storeId": "10", "integrationId": "your-integration-id" } ] }

Both fields — storeId and integrationId — are required per store item.

Possible rejected[].reason values:

ReasonMeaning
not_integratedThe store is not currently integrated.
invalid_integration_idThe integrationId does not match the store's current integration.

Response: 202 Accepted with the same batch body as provisioning.

If all stores are rejected, the API returns 422 Unprocessable Entity.

Full Flow Summary

[Once] Configure webhook → POST /clients/{clientId}/webhooks [Per batch] Retrieve Stores → GET /v2/stores/integration-status Activation → POST /v2/stores/provisioning (up to 20 stores per request) Await result → STORE_PROVISIONING_STATUS webhook [Optional] Deactivating → POST /v2/stores/deprovisioning

Error Response Format

All error responses from this API use a consistent JSON structure:

{ "message": "Human-readable description of the error" }

HTTP Status Codes

StatusWhen
400 Bad RequestInvalid request — missing fields, empty list, exceeds 20 stores, mixed integrations in batch
401 UnauthorizedMissing or invalid integrator token
403 ForbiddenMerchant token does not match the integration's integrationId, or merchant not authorized in Partners
422 Unprocessable EntityAll stores were rejected (none owned by the merchant)
424 Failed DependencyPartners service is unavailable
500 Internal Server ErrorUnexpected server error

Example — exceeding the store limit:

HTTP/1.1 400 Bad Request { "message": "stores list exceeds maximum allowed size of 20" }

Example — merchant not authorized:

HTTP/1.1 403 Forbidden { "message": "Merchant not authorized in Partners" }