# Ripple Collections
The Ripple Collection APIs are used to manage collections, manage payment channels, manage partners and settlements.
## API environments
The Ripple Collection APIs offers the following environments:
|
Environment
| Base URL | Description |
| ------------------------------------------ | ----------------------------- | ----------------------------------------- |
| Sandbox | `https://docs.ripple.com/products/collections/_mock/api/collections` | Sandbox environment with mock data which does not require auth.|
| UAT | `https://api.test.ripple.com` | UAT environment with simulated transactions. |
| Production | `https://api.ripple.com` | Production environment |
## API authentication
All API operations in UAT and Production require a Bearer access token specific to the environment you're using. Ripple provides a secure model for authentication and authorization by providing access tokens scoped for a set of credentials.
### Generate client ID and client secret
You will need your _client ID_ and _client secret_ to obtain an access token.
If you do not already have your client ID and client secret, do the following:
1. Log into the Ripple Collections UI. 2. In the left navigation menu, click **Settings**. 3. Under **Administration**, click **API Credentials**. 4. In the dropdown list next to the page title, select the access environment. For example, to provision credentials for the test environment, select **Test** from the dropdown list. 5. In the upper right corner of the page, click **New Credential**. 6. Click **Save and Generate Key**.
**Caution:** The *client secret* is displayed only once when you are creating new credentials. You cannot retrieve the secret after exiting this page. Copy and store the client secret securely and share it with authorized individuals in accordance with your organization's security policy.
You can now use the client ID and client secret to generate access tokens using the [Request an access token](/products/collections/api/collections/#operation/authenticate) operation.
### Request an access token
To get an access token, use the [Request an access token](/products/collections/api/collections/#operation/authenticate) operation with your `client_id` and `client_secret`. The response contains a token in the `access_token` field.
We recommend rotating your API credentials at regular intervals according to your organization's security policy.
**Note**: Authentication tokens are not a fixed length and can vary, avoid validating tokens based on character length.
## Idempotency
When a client supplies a unique key (`X-Idempotency-Key`, UUID). The header is **optional**: if it is omitted the request is processed normally (no deduplication, no replay). Supplying the header is strongly recommended for POST operations that create side effects.
Provide optional `X-Idempotency-Key` on POST writes for safe retries:
- Success → cached and replayable.
- In-flight duplicate → 409 (retry later).
- Mismatch → 422.
- Failure → entry removed (safe retry).
- Only successes are replayed.
### With vs Without the Header
With `X-Idempotency-Key`:
- First request claims and (if successful) caches response.
- Concurrent duplicates while in flight receive 409 + `Retry-After: 1`.
- Later identical retries get the cached 2xx response.
- Different payload/path/method using same key → 422 mismatch.
Without the header:
- Filter bypasses idempotency entirely.
- Each call executes independently; duplicates may create repeated side effects.
## Verifying Webhooks
When receiving webhooks, the signing operation has already been performed by the platform and you must validate the signature we provide to ensure the webhook originated from us.
When creating a subscription (using the subscription API), you are provided a `signature_verification_key`. This value is a base64‑encoded symmetric secret used to compute an HMAC-SHA256 signature for each webhook payload. You must persist this secret securely at subscription creation time.
**NOTE**: The returned `signature_verification_key` is base64-encoded binary. In the code sample below we base64‑decode it to raw bytes (you do NOT re‑encode it). You may display it in hex for debugging, but hex is not required for verification.
When a webhook is dispatched:
- We include `X-Webhook-Timestamp` (epoch milliseconds).
- We include `X-Webhook-Signature` in the form: `t=,v1=`.
- You reconstruct the exact signing string and verify.
### Verification is a 2 step process:
| Step
| Action | Description |
| ------------------------------------------ | ----------------------------- | ----------------------------------------- |
| 1 | Reconstruct | Concatenate the timestamp, a dot (`.`), and the SHA256 hex digest of the raw request body: `.`|
| 2 | Verify | HMAC-SHA256 the signing string with the decoded secret; compare the resulting hex digest to `v1` (constant-time). |
#### Fields Used in the Signature
| Field
| Description |
| ------------------------------------------ | ----------------------------- |
| Timestamp | Raw value from `X-Webhook-Timestamp` (epoch millis as a string). |
| Body | The exact raw HTTP request body bytes (no parsing / reformatting). |
| body_hash | `SHA256` hex digest of the raw body. |
| signing_string | `.` |
| Secret | Base64-decoded `signature_verification_key`. |
| Signature | Hex digest of `HMAC_SHA256(secret, signing_string)` placed in `v1`. |
If any of these values are transformed (e.g. JSON pretty-printed, numeric formatting changed, timestamp altered) the verification will fail.
#### Verification Example
```python import base64 import hashlib import hmac import time from typing import Optional
def verify_webhook(raw_body: bytes, timestamp: str, signature_header: str, base64_secret: str, max_age_seconds: int = 300) -> bool:
"""
Verify an incoming webhook.
Parameters:
raw_body: Exact raw request body bytes (no mutation).
timestamp: X-Webhook-Timestamp header (epoch millis as string).
signature_header:X-Webhook-Signature header in form "t=,v1=".
base64_secret: subscription signature_verification_key (base64).
max_age_seconds: Allowed clock skew / replay window (0 disables freshness check).
Returns: True if signature is valid, else False.
"""
if not (raw_body and timestamp and signature_header and base64_secret): return False
# Parse signature header
parts = {}
try:
for seg in signature_header.split(","):
k, v = seg.split("=", 1)
parts[k.strip()] = v.strip()
except Exception:
return False
if parts.get("t") != timestamp or "v1" not in parts:
return False
# Freshness (optional)
if max_age_seconds > 0 and timestamp.isdigit():
try:
ts_int = int(timestamp)
# Detect millis
if ts_int > 1_000_000_000_000:
ts_int //= 1000
if abs(int(time.time()) - ts_int) > max_age_seconds:
return False
except ValueError:
return False
# SHA256 hex of body
body_hash = hashlib.sha256(raw_body).hexdigest()
signing_string = f"{timestamp}.{body_hash}".encode("utf-8")
# Decode secret (single base64 decode only)
try:
secret = base64.b64decode(base64_secret, validate=True)
except Exception:
return False
expected = hmac.new(secret, signing_string, hashlib.sha256).hexdigest()
provided = parts["v1"]
# Constant-time compare
return hmac.compare_digest(expected, provided)
```
#### Flask Usage Example
```python
from flask import Flask, request, Response
import os
from verifier import verify_webhook # assuming the function above saved as verifier.py
app = Flask(__name__)
SECRET = os.environ.get("WEBHOOK_SECRET_VERIFICATION_KEY") # set from secure config
@app.post("/webhook")
def webhook():
raw_body = request.get_data() # exact bytes
ts = request.headers.get("X-Webhook-Timestamp", "")
sig = request.headers.get("X-Webhook-Signature", "")
if not verify_webhook(raw_body, ts, sig, SECRET, max_age_seconds=300):
return Response("invalid signature", status=400)
# Safe to parse AFTER verification
return Response("ok", status=200)
```
#### Key Points
- Always read raw bytes first; only parse JSON after verification.
- Do not pretty-print or modify the JSON before hashing.
- Reject if age window exceeded or headers malformed.
- Use `hmac.compare_digest` for constant-time comparison.
#### Common Pitfalls
| Issue | Cause | Resolution |
|-------|-------|-----------|
| signature_mismatch | Secret double-base64 encoded | Use original subscription value directly |
| signature_mismatch | Body re-serialized / pretty-printed | Hash exact raw bytes |
| signature_mismatch | Timestamp mismatch between `t` and header | Ensure they match verbatim |
| Stale timestamp | Clock skew or delayed processing | Check system clock; adjust max age |
| Invalid header format | Missing `t=` or `v1=` segment | Ensure exact format `t=...,v1=...` |
#### Checklist
- [ ] Captured raw body (not parsed then re-stringified)
- [ ] Extracted `X-Webhook-Timestamp`
- [ ] Parsed `X-Webhook-Signature` → t & v1
- [ ] Computed SHA256 hex of raw body
- [ ] Built signing string `.`
- [ ] Base64-decoded stored secret
- [ ] HMAC-SHA256 computed & constant-time compared
- [ ] (Optional) Freshness window passed
If every box is checked and comparison succeeds, the webhook is authentic.
Version: 0.1.0
## Servers
```
https://api.test.ripple.com
```
```
https://api.ripple.com
```
## Security
### Bearer
Type: http
Scheme: bearer
Bearer Format: JWT
## Download OpenAPI description
[Ripple Collections](https://docs.ripple.com/_bundle/products/collections/api/collections.yaml)
## Authentication
Endpoints for authentication
### Request an access token
- [POST /v2/oauth/token](https://docs.ripple.com/products/collections/api/collections/authentication/authenticate.md): Request an access token for authentication with Ripple APIs.
You need to request a token for the environment you want to authenticate with.
Note: The length of the access token isn't fixed, hence it can vary. Avoid validating tokens based on character length.
Tutorials
* Learn how to Request an access token.
#### Environments
| Environment | Domain | Description |
| --- | --- | --- |
| UAT | api.test.ripple.com | UAT environment with simulated transactions. |
| Production | api.ripple.com | Production environment |
### Test access token
- [GET /v2/oauth/token/test](https://docs.ripple.com/products/collections/api/collections/authentication/testauthtoken.md): Test if an access token can be used for authentication with Ripple APIs and how much time remains on it.
## Collection Links
Endpoints for managing one-time collection links
### List collections
- [GET /v0/collections/links](https://docs.ripple.com/products/collections/api/collections/collection-links/listcollections.md): Retrieves a list of collections, filterable by query parameters.
### Create a collection
- [POST /v0/collections/links](https://docs.ripple.com/products/collections/api/collections/collection-links/createcollection.md): Creates a new payment link for a collection.
### Get collection details
- [GET /v0/collections/links/{collection_id}](https://docs.ripple.com/products/collections/api/collections/collection-links/getcollectionbyid.md): Retrieves the details of a specific collection by its ID.
### Cancel a collection
- [POST /v0/collections/links/cancel](https://docs.ripple.com/products/collections/api/collections/collection-links/cancelcollection.md): API to cancel a payment collection.
## Collection Channels
Endpoints for managing persistent collection channels
### List channels
- [GET /v0/collections/channels](https://docs.ripple.com/products/collections/api/collections/collection-channels/listchannels.md): Retrieves a list of channels, filterable by query parameters.
### Create a channel
- [POST /v0/collections/channels](https://docs.ripple.com/products/collections/api/collections/collection-channels/createchannel.md): Creates a new payment channel between payer and beneficiary.
### Get channel details
- [GET /v0/collections/channels/{channel_id}](https://docs.ripple.com/products/collections/api/collections/collection-channels/getchannelbyid.md): Retrieves the details of a specific channel by its ID.
## Partners
Endpoints for managing partners who can transact
### List partners
- [GET /v0/collections/partners](https://docs.ripple.com/products/collections/api/collections/partners/listpartners.md): Retrieves a list of partners, filterable by ID or type.
### Create a partner
- [POST /v0/collections/partners](https://docs.ripple.com/products/collections/api/collections/partners/createpartner.md): This API allows users to add new parties who can transact.
### Get partner by ID
- [GET /v0/collections/partners/{partner_id}](https://docs.ripple.com/products/collections/api/collections/partners/getpartnerbyid.md): This API allows users to retrieve a specific partner by their ID.
### Update a partner
- [PUT /v0/collections/partners/{partner_id}](https://docs.ripple.com/products/collections/api/collections/partners/updatepartner.md): API to update a partner to use in collections API.
## Settlements
Endpoints for managing settlements
### List settlements
- [GET /v0/collections/settlements](https://docs.ripple.com/products/collections/api/collections/settlements/listsettlements.md): API to fetch first party payouts (settlements).
### Get settlement by ID
- [GET /v0/collections/settlements/{settlement_id}](https://docs.ripple.com/products/collections/api/collections/settlements/getsettlementbyid.md): API to fetch a first party payout (settlement to our customer).
## Transactions
Endpoints for viewing transactions across collection links and channels
### List Transactions
- [GET /v0/collections/transactions](https://docs.ripple.com/products/collections/api/collections/transactions/listtransactions.md): API to fetch transactions across collections links or channels (transactions).
### Get transaction by ID
- [GET /v0/collections/transactions/{transaction_id}](https://docs.ripple.com/products/collections/api/collections/transactions/gettransactionbyid.md): API to fetch a transaction (payment from a payer to beneficiary)
## Webhooks
Endpoints for managing webhook registrations
### List webhooks
- [GET /v0/collections/webhooks](https://docs.ripple.com/products/collections/api/collections/webhooks/listwebhooks.md)
### Create webhooks
- [POST /v0/collections/webhooks](https://docs.ripple.com/products/collections/api/collections/webhooks/createwebhooks.md): Creates one webhook per event type supplied.
### Get a webhook
- [GET /v0/collections/webhooks/{webhook_id}](https://docs.ripple.com/products/collections/api/collections/webhooks/getwebhook.md)
### Update a webhook
- [PUT /v0/collections/webhooks/{webhook_id}](https://docs.ripple.com/products/collections/api/collections/webhooks/updatewebhook.md)
### Delete a webhook
- [DELETE /v0/collections/webhooks/{webhook_id}](https://docs.ripple.com/products/collections/api/collections/webhooks/deletewebhook.md)