ribbonPay Direct/API Integration Guide
This guide is the published Direct/API documentation (also served at /docs). The interactive OpenAPI reference is at /docs/api; the machine-readable spec is at /openapi.json. Credentials are provisioned by ribbonPay ops — there is no self-serve signup.
This guide is for merchants whose checkout or order-management system connects to ribbonPay over HTTPS — custom carts, Adobe Commerce (Magento), BigCommerce custom flows, headless storefronts, and any platform where your developers call our REST API directly.
Not for Shopify. Shopify merchants install the ribbonPay app; orders arrive via Shopify webhooks and decisions are written back to Shopify automatically. This guide covers the Direct/API path only (the same API the WooCommerce plugin uses under the hood).
1. Integration model
ribbonPay is a server-to-server integration. Your backend calls ribbonPay when an order is placed, holds fulfillment until ribbonPay approves it, and acts on the decision we return.
- Your backend
POSTs the order to/api/v1/orderswith a Bearer API key. - ribbonPay returns
200withdecisionPENDING,SHIP, orCANCEL. - Hold capture and fulfillment until you receive
SHIP. - Optionally
PATCHlate gateway data (AVS, CVV, BIN). - Analysts review
PENDINGorders. - ribbonPay
POSTs a signed webhook with the finalSHIPorCANCEL. - On
SHIP, fulfill; onCANCEL, void or refund.
When to call ribbonPay
Call POST /api/v1/orders immediately after payment authorization succeeds and before you capture funds or ship goods. Your integration must:
- Authorize payment with your gateway (do not capture yet, unless your business model requires otherwise).
- Submit the order to ribbonPay and read the synchronous response.
- Hold fulfillment until you receive an explicit
SHIPdecision (synchronous response or later webhook). - Act on the final decision:
SHIP— capture payment (if not already captured) and fulfill the order.CANCEL— void authorization, refund, or cancel the order per your gateway workflow.PENDING— keep the order on hold; a human analyst will review it. The final decision arrives via signed webhook (see §7).
Training period (new merchants)
During the initial onboarding period (~90 days), ribbonPay routes orders to human review by default, so the synchronous API response will be PENDING for most orders. Automatic decisions are allowed during training: as our analysts identify reliable signals for your store, they may introduce auto-approve or auto-cancel rules, so your integration must handle a synchronous SHIP or CANCEL from day one. You must also implement the webhook receiver — that is how you receive SHIP and CANCEL after analyst review.
2. What ribbonPay provides / what we need from you
ribbonPay provides (integration packet)
| Item | Description |
|---|---|
| API base URL | Production host for all requests (provided at onboarding), e.g. https://<your-ribbonpay-host> |
| API key | Secret bearer token, format rp_live_ + 32 characters (40 characters total). Shown once at provisioning. Store server-side only. |
| Webhook signing secret | Per-merchant secret used to verify decision callbacks (HMAC-SHA256). Provided at onboarding. |
| Field contract | Required and optional order fields (§4). We may configure decision_required_fields for your account — fields you must supply before scoring runs. |
| Support contact | ribbonPay integration/onboarding contact for credential and field-contract questions |
We need from you
| Item | Description |
|---|---|
| Webhook receiver URL | HTTPS endpoint on your infrastructure that accepts POST decision callbacks. Required for orders that go to analyst review. |
| Field availability matrix | Which payment, customer, and device fields your cart/gateway can supply at order creation vs. a few seconds later (for PATCH). |
| Fulfillment-hold confirmation | Written confirmation that you will not capture/ship until SHIP. |
| Test credentials / sandbox orders | Ability to place test orders in a staging environment before go-live (coordinate with ribbonPay). |
3. Authentication
Every API request must include your API key in the Authorization header:
Authorization: Bearer rp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
| Rule | Detail |
|---|---|
| Key format | Must start with rp_live_ and be exactly 40 characters |
| Storage | Server-side only — never embed in browser JavaScript, mobile apps, or public repos |
| Scope | One API key identifies one merchant account; you can only read/update your own orders |
Authentication errors (401)
reason | Meaning |
|---|---|
missing_header | No Authorization: Bearer … header |
invalid_format | Key does not match rp_live_ + 32-char pattern |
not_found | Key is revoked or unknown |
inactive | Merchant account or key is inactive |
All responses include an X-Request-Id header — include it when contacting ribbonPay support.
4. Submit an order — POST /api/v1/orders
POST /api/v1/orders
Creates (or idempotently returns) an order and runs enrichment, scoring, and rules. Returns a synchronous decision when data is complete.
Required fields
| Field | Type | Constraints |
|---|---|---|
external_order_id | string | Your order ID. Idempotency key — safe to retry with the same value. |
customer_email | string | Valid email |
billing_address | object | See address shape below |
shipping_address | object | See address shape below |
ip_address | string | Shopper IP at checkout (not your server IP) |
line_items | array | Min 1 item; see line item shape below |
order_total | number | ≥ 0 |
Address shape (both billing and shipping):
| Key | Type | Constraints |
|---|---|---|
street | string | Min 1 character |
city | string | Min 1 character |
state | string | Min 1 character |
zip | string | Min 1 character |
country | string | ISO 3166-1 alpha-2 (e.g. "US") — uppercased on ingest |
Line item shape:
| Key | Type | Constraints |
|---|---|---|
sku | string | Min 1 character |
quantity | integer | > 0 |
price | number | ≥ 0 |
description | string | Optional, max 500 characters |
Optional fields (recommended for accurate scoring)
Send everything your gateway and cart can provide. More payment and customer context improves review quality.
| Field | Type | Constraints / notes |
|---|---|---|
customer_name | string | |
customer_phone | string | |
billing_name | string | |
billing_phone | string | |
order_subtotal | number | ≥ 0 |
order_tax | number | ≥ 0 |
order_shipping | number | ≥ 0 |
currency | string | 3-letter ISO code (default USD) |
order_placed_at | string | ISO 8601 timestamp |
discount_total | number | ≥ 0 |
discount_codes | string[] | |
channel | string | Sales channel / source |
gateway | string | Payment gateway name |
payment_method | string | e.g. credit_card, paypal |
ship_method | string | e.g. UPS Ground |
order_notes | string | |
order_status | string | Your platform order status |
payment_status | string | Gateway payment status |
avs_code | string | Address Verification Service code |
cvv_code | string | CVV/CVC result code |
card_bin | string | Exactly 6 digits |
card_last4 | string | Exactly 4 digits |
card_brand | string | e.g. visa, mastercard |
card_exp_month | integer | 1–12 |
card_exp_year | integer | 2000–2100 |
auth_result | string | Gateway authorization result |
three_ds_result | string | 3-D Secure outcome |
cavv_result | string | CAVV / liability shift indicator |
customer_created_at | string | ISO 8601 — customer account creation |
customer_first_order_at | string | ISO 8601 |
customer_last_order_at | string | ISO 8601 |
customer_order_count | integer | ≥ 0 |
customer_total_spend | number | ≥ 0 |
shipping_recipient_name | string | If different from buyer |
shipping_recipient_email | string | Valid email |
shipping_recipient_phone | string | |
device_id | string | Device fingerprint ID if you collect one |
device_session_id | string | Per-checkout session id from the ribbonPay browser collector (max 64 chars); joins collector signals to this order |
device_lat | number | −90 to 90; must be sent with device_lng |
device_lng | number | −180 to 180; must be sent with device_lat |
Unknown fields are rejected (400 invalid_request). Send only fields listed above.
Minimal request example
{
"external_order_id": "ORD-10042",
"customer_email": "buyer@example.com",
"billing_address": {
"street": "1 Main St",
"city": "Austin",
"state": "TX",
"zip": "78701",
"country": "US"
},
"shipping_address": {
"street": "1 Main St",
"city": "Austin",
"state": "TX",
"zip": "78701",
"country": "US"
},
"ip_address": "203.0.113.45",
"line_items": [
{ "sku": "WIDGET-01", "quantity": 1, "price": 49.99 }
],
"order_total": 49.99
}
Full request example (with payment data)
{
"external_order_id": "ORD-10042",
"customer_email": "buyer@example.com",
"customer_name": "Jane Buyer",
"customer_phone": "+15125550100",
"billing_address": {
"street": "1 Main St",
"city": "Austin",
"state": "TX",
"zip": "78701",
"country": "US"
},
"shipping_address": {
"street": "99 Oak Ave",
"city": "Dallas",
"state": "TX",
"zip": "75201",
"country": "US"
},
"ip_address": "203.0.113.45",
"line_items": [
{
"sku": "WIDGET-01",
"quantity": 2,
"price": 24.99,
"description": "Premium widget"
}
],
"order_total": 54.98,
"order_subtotal": 49.98,
"order_tax": 5.00,
"currency": "USD",
"order_placed_at": "2026-06-29T15:30:00Z",
"gateway": "authorize_net",
"payment_method": "credit_card",
"payment_status": "authorized",
"avs_code": "Y",
"cvv_code": "M",
"card_bin": "411111",
"card_last4": "1111",
"card_brand": "visa",
"card_exp_month": 12,
"card_exp_year": 2028,
"customer_order_count": 3,
"customer_total_spend": 450.00,
"ship_method": "UPS Ground"
}
Success response (200)
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"decision": "PENDING",
"reason_code": "no_decision_rule",
"timestamp": "2026-06-29T15:30:01.123Z"
}
| Field | Description |
|---|---|
order_id | ribbonPay internal UUID — store for support correlation |
decision | SHIP, CANCEL, or PENDING |
reason_code | Machine-readable reason for the decision |
timestamp | Order creation time (ISO 8601) |
awaiting_fields | Present when required fields are missing (see below) |
Awaiting data (awaiting_fields)
If your merchant account has a decision_required_fields contract and the payload is missing those fields, the order is stored but not scored yet:
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"decision": "PENDING",
"reason_code": "awaiting_data",
"timestamp": "2026-06-29T15:30:01.123Z",
"awaiting_fields": ["avs_code", "card_bin"]
}
Send the missing fields via PATCH /api/v1/orders/{external_order_id} (§5). When complete, ribbonPay runs enrichment and decision automatically.
curl example
curl -X POST "https://<your-ribbonpay-host>/api/v1/orders" \
-H "Authorization: Bearer rp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d @order.json
5. Backfill / update — PATCH /api/v1/orders/{external_order_id}
PATCH /api/v1/orders/{external_order_id}
Send only the fields you want to add or correct. Use this when gateway data (AVS, CVV, BIN) arrives after the initial order submission — the same pattern used by the WooCommerce + Authorize.net integration.
Rules
| Rule | Detail |
|---|---|
| Identity | external_order_id is in the URL; customer_email cannot be changed after create |
| Body | At least one updatable field required; unknown fields rejected |
| Same validators | card_bin (6 digits), card_last4 (4 digits), address shape, etc. |
device_lat / device_lng | Must be sent together if either is sent |
Updatable fields
All optional fields from §4 except external_order_id and customer_email (includes device_session_id for late collector correlation).
Common PATCH payloads:
{
"avs_code": "Y",
"cvv_code": "M",
"card_bin": "411111",
"card_last4": "1111",
"card_brand": "visa",
"payment_status": "authorized"
}
Response shapes (200)
The response includes updated_fields (list of field names applied) and varies by outcome:
Still awaiting required fields:
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "awaiting_data",
"awaiting_fields": ["card_bin"],
"updated_fields": ["avs_code", "cvv_code"]
}
Data merged, order not yet complete for decision (no contract transition):
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"decision": "PENDING",
"reason_code": "awaiting_data",
"risk_score": 0,
"timestamp": "2026-06-29T15:30:05.000Z",
"updated_fields": ["gateway"]
}
Completeness reached — decision run:
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"decision": "PENDING",
"reason_code": "no_decision_rule",
"risk_score": 42,
"timestamp": "2026-06-29T15:30:05.000Z",
"updated_fields": ["avs_code", "card_bin"]
}
Order already decided (SHIP or CANCEL) — fields merged, decision unchanged:
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"decision": "SHIP",
"reason_code": "analyst_ship",
"risk_score": 15,
"timestamp": "2026-06-29T16:00:00.000Z",
"updated_fields": ["ship_method"],
"status": "merged_post_decision"
}
Payment error detected (via payment_status):
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "payment_error",
"reason_code": "payment_error",
"timestamp": "2026-06-29T15:30:05.000Z",
"updated_fields": ["payment_status"]
}
Merchant cancelled (via order_status or payment_status):
{
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "merchant_cancelled",
"reason_code": "merchant_cancelled",
"timestamp": "2026-06-29T15:30:05.000Z",
"updated_fields": ["order_status"]
}
Errors
| Status | Meaning |
|---|---|
404 | No order with that external_order_id for your merchant |
400 | Validation error (empty body, bad field format, unknown fields) |
401 | Authentication failure |
429 | Rate limit exceeded |
500 | Internal error — safe to retry PATCH for idempotent field updates |
6. Decision handling
| Decision | Meaning | Your action |
|---|---|---|
SHIP | Approve for fulfillment | Capture payment (if held) and ship |
CANCEL | Decline / do not fulfill | Void, refund, or cancel the order |
PENDING | Under review | Do not ship. Wait for webhook with final decision |
Where decisions arrive
| Path | When |
|---|---|
Synchronous — POST /api/v1/orders response | Immediate, when data is complete and rules auto-decide (less common during the training period, but possible once auto-decision rules are introduced) |
Synchronous — PATCH response | When a previously incomplete order becomes complete and rules auto-decide |
| Asynchronous — signed webhook (§7) | After analyst review, or any SHIP / CANCEL that was not returned synchronously |
Webhooks are sent only for SHIP and CANCEL — never for PENDING.
If the synchronous response is PENDING, you must implement the webhook receiver to get the final outcome.
Common reason_code values
reason_code | Typical meaning |
|---|---|
no_decision_rule | No auto-decision rule matched; order queued for analyst review (normal during onboarding) |
awaiting_data | Required fields missing; send PATCH when data is available |
payment_error | Gateway reported a hard payment failure |
merchant_cancelled | Order was cancelled/refunded/voided on your side |
rule_match | An automated rule decided the order |
analyst_ship / analyst_cancel | Human analyst approved or declined |
enrichment_failure_pending | External enrichment unavailable; queued for review |
7. Receiving decisions (webhook)
ribbonPay POSTs to the webhook_url configured on your merchant account when an order receives a final SHIP or CANCEL decision.
Request from ribbonPay
POST https://your-site.com/ribbonpay/webhook
Content-Type: application/json
X-Ribbonpay-Event: order.decision
X-Ribbonpay-Signature: <hex-encoded HMAC-SHA256>
X-Ribbonpay-Signature-Previous: <hex> # only during the 7-day window after secret rotation
X-Ribbonpay-Delivery-Id: <uuid> # present on deliveries created after webhook_deliveries rollout
Body:
{
"event": "order.decision",
"event_id": "a1b2c3d4e5f6789012345678abcdef01",
"order_id": "550e8400-e29b-41d4-a716-446655440000",
"external_order_id": "ORD-10042",
"decision": "SHIP",
"reason_code": "analyst_ship",
"timestamp": "2026-06-29T16:45:00.000Z"
}
| Field | Description |
|---|---|
event | Always order.decision |
event_id | Stable dedup key for this decision event (32-char hex). Same across QStash redeliveries and ops manual retries of the same decision; changes if the order is decided again with a new decided_at. |
order_id | ribbonPay internal UUID |
external_order_id | Your order ID — use this to match your order record |
decision | SHIP or CANCEL |
reason_code | Machine-readable reason |
timestamp | When the decision was recorded (ISO 8601) |
At-least-once delivery: queue delivery can deliver the same event more than once. Deduplicate on event_id (preferred) before applying the decision. The optional X-Ribbonpay-Delivery-Id header identifies a specific QStash delivery attempt/message for tracing — do not use it as the business dedup key.
Signature verification
- Read the raw request body as a string (before JSON parsing).
- Compute HMAC-SHA256 of that raw body using your webhook signing secret.
- Compare the result (hex-encoded, lowercase) to the
X-Ribbonpay-Signatureheader using a constant-time comparison. - During secret rotation, also accept a match against
X-Ribbonpay-Signature-Previous(present for 7 days after ribbonPay rotates your signing secret). Accept the request if either signature verifies. - Reject the request if neither signature matches.
The request body is sent as canonical JSON (object keys sorted alphabetically at every nesting level). Verify against the exact bytes received — do not re-serialize parsed JSON unless your serializer produces identical output.
Node.js example
import { createHmac, timingSafeEqual } from 'crypto'
function verifyRibbonpayWebhook(rawBody, signatureHeader, secret) {
const expected = createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex')
const received = signatureHeader.trim()
if (expected.length !== received.length) return false
return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex')
)
}
// Express: use express.raw({ type: 'application/json' }) or req.rawBody
// Then:
// if (!verifyRibbonpayWebhook(rawBody, req.headers['x-ribbonpay-signature'], WEBHOOK_SECRET)) {
// return res.status(401).send('Invalid signature')
// }
PHP example (Magento / WooCommerce-style backends)
<?php
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_RIBBONPAY_SIGNATURE'] ?? '';
$secret = getenv('RIBBONPAY_WEBHOOK_SECRET');
$expected = hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($rawBody, true);
$externalOrderId = $payload['external_order_id'];
$decision = $payload['decision']; // SHIP or CANCEL
Your webhook handler must
| Requirement | Detail |
|---|---|
Respond 2xx quickly | Acknowledge receipt; do heavy work asynchronously if needed |
| Be idempotent | ribbonPay retries on network errors and 5xx responses — the same decision may be delivered more than once |
Use external_order_id | Primary key to locate your order |
| Verify signature before acting | Never fulfill or cancel based on an unverified payload |
Retry behavior
| Your response | ribbonPay behavior |
|---|---|
2xx or 4xx | Delivery considered complete (fix 4xx bugs on your side) |
5xx or network timeout | Retried automatically |
8. Edge cases and status reference
Idempotency
external_order_id is unique per merchant. Submitting the same ID twice returns the original order and decision (200) — no duplicate row is created. Safe to retry on network failures.
Payment error (payment_error)
If payment_status is one of declined, failed, error, or refused (case-insensitive), the order is recorded with status payment_error and is not scored for fraud review. No fulfillment action is expected from ribbonPay.
Merchant cancelled (merchant_cancelled)
If payment_status or order_status is cancelled, refunded, or voided (case-insensitive), the order is recorded as merchant_cancelled — the shopper or merchant cancelled before ribbonPay could decide.
Rate limiting (429)
Default limit: 60 requests per minute per merchant (configurable). Response includes:
{ "error": "rate_limited", "retry_after_seconds": 12 }
Header: Retry-After (seconds). Back off and retry.
Validation errors (400)
{
"error": "invalid_request",
"details": [ /* Zod issue objects */ ]
}
Common causes: missing required field, invalid email, country not 2 letters, card_bin not 6 digits, device_lat without device_lng, empty PATCH body, unknown JSON keys.
Error summary
| HTTP | error | Action |
|---|---|---|
400 | invalid_request | Fix payload |
401 | unauthorized | Check API key |
404 | not_found | PATCH target order does not exist |
429 | rate_limited | Retry after Retry-After |
500 | internal_error | Retry POST (idempotent) or contact support with X-Request-Id |
9. Testing checklist
Use Postman, curl, or your HTTP client against your ribbonPay environment. Confirm each scenario and verify the order appears in the ribbonPay ops console.
Prerequisites
- Merchant account provisioned with API key and webhook signing secret
-
webhook_urlpointed at a test receiver (webhook.site, RequestBin, or local tunnel) - Ops console access to resolve PENDING orders (for webhook end-to-end test)
POST /api/v1/orders
| # | Scenario | Expected |
|---|---|---|
| 1 | Minimal valid payload | 200, decision (typically PENDING during training) |
| 2 | Full payload with AVS/CVV/BIN/card fields | 200, order stored with payment data |
| 3 | Missing required field (e.g. ip_address) | 400 invalid_request |
| 4 | Invalid card_bin (not 6 digits) | 400 invalid_request |
| 5 | device_lat without device_lng | 400 invalid_request |
| 6 | Missing fields in your decision_required_fields contract | 200, awaiting_fields array present |
| 7 | "payment_status": "declined" | 200, decision: PENDING, reason_code: payment_error |
| 8 | "order_status": "cancelled" | 200, decision: PENDING, reason_code: merchant_cancelled |
| 9 | Same external_order_id submitted twice | 200, same order_id, no duplicate |
| 10 | Missing / invalid API key | 401 unauthorized |
| 11 | Malformed JSON body | 400 invalid_request |
PATCH /api/v1/orders/{external_order_id}
| # | Scenario | Expected |
|---|---|---|
| 12 | PATCH AVS/CVV/BIN onto order from #6 | 200, updated_fields; may trigger decision when complete |
| 13 | PATCH unknown field | 400 invalid_request |
| 14 | PATCH nonexistent external_order_id | 404 not_found |
| 15 | PATCH after order decided SHIP/CANCEL | 200, status: merged_post_decision, decision unchanged |
Webhook receiver
| # | Scenario | Expected |
|---|---|---|
| 16 | Resolve a PENDING order as SHIP in ops console | Webhook POST received with decision: SHIP |
| 17 | Verify X-Ribbonpay-Signature with your signing secret | Signature validates against raw body |
| 18 | Return 200 from your handler | Delivery logged as successful |
| 19 | Resolve order as CANCEL | Webhook with decision: CANCEL; your system cancels/voids |
End-to-end flow
| # | Scenario | Expected |
|---|---|---|
| 20 | POST order → hold fulfillment → receive SHIP webhook → capture/ship | Full happy path |
| 21 | POST order → receive CANCEL webhook → void/refund | Full decline path |
| 22 | POST incomplete → PATCH gateway data → decision runs | Two-step intake path |
9. Device collection (browser collector)
ribbonPay ships a hosted browser collector for Direct/API merchants and the WooCommerce plugin. It follows a Kount DDC-style pattern: the shopper's browser posts device signals keyed by a per-checkout session_id; your order POST/PATCH carries the same value as device_session_id; ribbonPay joins them server-side and derives a stable device_id fingerprint.
Shopify checkout uses a separate no-code Custom Pixel that feeds the same ingest endpoint — see shopify-device-collection-setup.md (ops-installed; not something the merchant integrates via this guide).
Site key
Ops provisions a publishable site key (rp_site_ + random suffix). This is not your secret rp_live_… API key. Never embed the live API key in storefront HTML.
Checkout embed
Generate a fresh session_id per checkout (UUID recommended). Set it in a first-party cookie if useful for your plugin, and pass the same value as device_session_id on POST /api/v1/orders.
<script
src="https://YOUR_RIBBONPAY_HOST/device/v1/collector.js"
data-rp-site-key="rp_site_xxxxxxxxxxxxxxxxxxxxxxxx"
data-rp-session="YOUR_CHECKOUT_SESSION_ID"
data-rp-ingest="https://YOUR_RIBBONPAY_HOST/api/device/v1/collect"
async
></script>
Alternative config object (if you load the script programmatically):
window.ribbonpayDevice = {
siteKey: 'rp_site_…',
sessionId: 'YOUR_CHECKOUT_SESSION_ID',
ingestUrl: 'https://YOUR_RIBBONPAY_HOST/api/device/v1/collect',
}
Ingest contract
| Item | Detail |
|---|---|
| Endpoint | POST /api/device/v1/collect |
| Auth | site_key in JSON body (publishable key) |
| CORS | Open (Access-Control-Allow-Origin: *); no cookies/credentials |
| Body | { "site_key", "session_id", "client_token?", "signals" } |
| Order join | Same session_id as device_session_id on the order API |
Collection may arrive before or after the order POST; both orderings are supported. If signals arrive late, ribbonPay backfills device enrichment on pending review orders.
Order field
{
"external_order_id": "ORD-10042",
"device_session_id": "YOUR_CHECKOUT_SESSION_ID",
"...": "..."
}
ribbonPay derives device_id from collected signals. Merchants may still send their own device_id; merchant-supplied values take precedence.
10. Known limitations
| Limitation | Detail |
|---|---|
| Shopify device collection | Browser collector + device_session_id apply to Direct/API and WooCommerce plugin paths only. Shopify needs a separate web-pixel integration (TBD). |
| No pull from your cart | Unlike Shopify (where ribbonPay pulls payment data via Admin API), Direct/API merchants push all data. If a field is not in your POST or PATCH, ribbonPay cannot retrieve it from your platform. |
| Training period | Expect synchronous PENDING on most orders; auto-approve/auto-cancel rules may be introduced during training, so handle synchronous SHIP/CANCEL too. Webhook implementation is mandatory before go-live. |
Quick reference
| Operation | Method | Path |
|---|---|---|
| Submit order | POST | /api/v1/orders |
| Update / backfill | PATCH | /api/v1/orders/{external_order_id} |
| Device collector JS | GET | /device/v1/collector.js |
| Device signal ingest | POST | /api/device/v1/collect |
| Health check | GET | /api/health |
Auth: Authorization: Bearer rp_live_…
Decision webhook headers: X-Ribbonpay-Signature, optional X-Ribbonpay-Signature-Previous (rotation grace), X-Ribbonpay-Event: order.decision, optional X-Ribbonpay-Delivery-Id
Support: Include X-Request-Id from any API response when contacting ribbonPay.