Quickstart
This quickstart walks through the happy-path integration loop: obtain a token, create a charge, upload a support file, and observe the webhook callback when DVS finishes validating it.
By the end, you'll have:
- A valid sandbox
access_tokencached locally. - A
support_file_idfor a document uploaded against an existing account. - A real
DOCUMENT_VALIDATION_COMPLETED(orDOCUMENT_VALIDATION_REJECTED) webhook event in your receiver's logs.
Prerequisites
- 1Sandbox credentials from OSIGU
You need
client_id,client_secret, provider_slug, and a webhook secret. Get them from OSIGU's onboarding team before you start. - 2A valid agreement code
Ask OSIGU support for a
(provider, payer, agreement_code)combination configured for your sandboxclient_id. You'll use theagreement_codein every charge request. The Event and Account are created automatically when you send your first charge — you don't need anything seeded. - 3A publicly reachable HTTPS endpoint for webhooks
Local dev: use ngrok or cloudflared to expose
http://localhost:3000over HTTPS. Tell OSIGU the public URL so they wire it as your sandbox webhook destination.
1. Get an access token
OAuth2 client credentials grant against the OSIGU SSO server:
curl -X POST https://sandbox.osigu.com/v1/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials"
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "bearer",
"expires_in": 3600,
"scope": "rcm:read rcm:write"
}
Cache the access_token for ~50 minutes (default TTL is 60). Don't fetch a fresh one per request — the auth server rate-limits clients that do.
export TOKEN="eyJhbGciOiJSUzI1NiIs..."
2. Send your first charge
A charge is a billable line item attached to an event (the encounter). You don't create the event explicitly — RCM creates it implicitly from the origin_event_id and patient info you include in the charge request. See Events and Charges for the model.
Use the origin_event_id OSIGU seeded for you:
curl -X POST https://sandbox.osigu.com/rcm/v1/charges \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "QS-0001",
"origin_event_id": "EVT-SANDBOX-12345",
"agreement_code": "DEFAULT",
"provider_product_code": "10101012",
"creation_date_time": "2026-06-25T14:00:00Z",
"description": "Consulta médica em consultório",
"currency": "BRL",
"quantity": 1,
"amount": 120.00,
"total_amount": 120.00,
"patient_id": "PAT-001",
"patient_name": "MARIA SILVA DOS SANTOS"
}'
Response (200 OK):
{
"code": "QS-0001",
"origin_event_id": "EVT-SANDBOX-12345",
"agreement_code": "DEFAULT",
"status": "OPEN",
"currency": "BRL",
"total_amount": 120.00,
"created_at": "2026-06-25T14:01:23.456Z"
}
The charge is now OPEN — modifiable, not yet linked to an invoice. Under the hood RCM has created (or reused) the Event for origin_event_id=EVT-SANDBOX-12345 and the Account that groups it.
3. Upload a support file
A support file is the document that justifies the charge for the payer. The upload endpoint takes a multipart with two parts: the file binary and a request_data JSON describing which event/charges it backs:
curl -X POST https://sandbox.osigu.com/rcm/v1/support-files/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@./guia_tiss_test.pdf" \
-F 'request_data={
"support_file_code": "TISS_GUIDE",
"events": [
{
"origin_event_id": "EVT-SANDBOX-12345",
"agreement_code": "DEFAULT",
"charge_codes": ["QS-0001"]
}
]
};type=application/json'
The events array means one upload can back multiple events at once (and inside each, multiple charges). For most cases you'll have a single entry. Omit charge_codes to attach the file at event level — useful when the document covers the whole encounter rather than one specific procedure.
Response (202 Accepted):
{
"support_file_id": "sf_01HX9KP7VQ2D4FZ8N0YPRQAW6X",
"uploaded_at": "2026-06-25T14:02:11.789Z",
"status": "PENDING_VALIDATION"
}
RCM hands the file to DVS in the background — the API call returns immediately. Do not poll for the result; wait for the webhook.
Don't know the document type? Use the v2 endpoint with
support_file_codeomitted — RCM auto-classifies via DVS, then validates.
4. Receive the validation webhook
While the upload is processing, RCM will POST a webhook to your registered URL. For this charge, you'll get one of:
DOCUMENT_VALIDATION_COMPLETED→validation_status: APPROVED, the document passed all rules. The charge is now eligible to advance.DOCUMENT_VALIDATION_REJECTED→validation_status: REJECTED, one or more rules failed (see theerrorsarray). The charge is blocked until the document is corrected and re-uploaded.
The body (excerpt for DOCUMENT_VALIDATION_COMPLETED):
{
"event_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"event_type": "DOCUMENT_VALIDATION_COMPLETED",
"entity_type": "support_file",
"entity_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"payload": {
"document_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"support_file_code": "SF-2025-001234",
"document_level": "ACCOUNT",
"account_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"provider_slug": "clinica-del-norte",
"validation_status": "APPROVED",
"precision_percentage": 99.1,
"analysis_type_applied": "OCR_FULL",
"document_type_code": "INVOICE",
"errors": [],
"validated_at": "2026-06-25T14:02:38.456-06:00"
},
"metadata": {},
"created_at": "2026-06-25T14:02:42.123-06:00"
}
Before processing the body, verify the HMAC signature. Quick-and-dirty check (Node.js):
import crypto from 'crypto';
function verify(rawBody, headers, secret) {
const ts = headers['x-rcm-timestamp'];
const sig = headers['x-rcm-signature'];
const expected = crypto
.createHmac('sha256', secret)
.update(`${ts}${rawBody}`) // no separator
.digest('base64');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Production-quality snippets in Python, Java, Go, PHP, and Ruby are in Signature verification.
5. What to do next
You've successfully completed the integration loop. From here:
- Implement a proper webhook receiver — idempotency, retry handling, dead-letter alerts. See Implement a webhook receiver.
- Handle
DOCUMENT_VALIDATION_REJECTEDevents — show theerrorsto the end user so they can correct the document. See Webhook events → Validation. - Move on to invoicing — once all required support files for an account are validated, assign its charges to an invoice via
POST /v1/invoices/assign-charges. - Try the v2 upload with auto-classification — if your system doesn't know the document type in advance, the v2 endpoint lets RCM classify via DVS before validating.
Troubleshooting
| Symptom | Likely cause |
|---|---|
401 Unauthorized on /oauth/token | client_id or client_secret is wrong, or you're hitting the wrong environment's OAuth URL. |
401 Unauthorized on /v1/charges | The token expired (try refreshing), OR you used a token from a different environment. |
403 Forbidden on /v1/charges | The token is valid but lacks the scope for this action — confirm with OSIGU which scopes your client was granted. |
422 Validation error on POST /v1/charges | Schema mismatch — check the API reference for required fields. |
Upload returns 202 but no webhook arrives | Your webhook URL isn't registered for this sandbox client_id, or your endpoint is returning non-2xx (check your receiver's logs). |
| Webhook arrives but signature verification fails | You're using a re-serialised body, not the raw bytes. See signature verification gotchas. |
Stuck? Email support@osigu.com with your support_file_id and dvs_validation_request_id — we can trace exactly what happened.