Skip to main content

Common Workflows

Processing a payment

Before submitting a payment, validate the payload with POST /api/payments/validate. This returns the detected payment channel and an estimated fee without initiating a transfer. It's worth doing this step on every payment to surface errors early.

Once validated, create the payment with POST /api/payments. Always include an X-Idempotency-Key header. SELVA uses it to deduplicate requests, so if a network failure causes you to retry, you won't process the same payment twice.

Payments are processed asynchronously. Poll GET /api/payments/{id} to check the current state, or set up a webhook subscription to receive status updates without polling.

Managing accounts

Create an account with POST /api/accounts, specifying either CRC or USD as the currency. Once created, use GET /api/accounts to list accounts, GET /api/accounts/{id} to inspect account details, and GET /api/accounts/{id}/balance to retrieve the current balance.

Verifying a destination

Before sending a payment, you can confirm the destination is valid. GET /api/iban/information/{iban} returns the account holder's name for a given IBAN. GET /api/phone/information/{phone} confirms whether a phone number is registered for SINPE Móvil and returns the holder's name.

Doing this before creating a payment gives your users a chance to confirm they're sending money to the right person.

Setting up webhooks

Register a URL to receive events with POST /api/webhooks/subscriptions. Subscriptions can include the target URL, subscribed events, retry settings, timeout, and optional headers.

Subscriptions can be suspended and resumed independently via POST /api/webhooks/subscriptions/{id}/suspend and POST /api/webhooks/subscriptions/{id}/resume. This lets you take an endpoint offline temporarily without losing the subscription configuration.

Incoming notification payloads

SELVA sends subscribed events to your webhook URL as JSON. Every incoming notification includes the event name, the payment or transaction data, and the delivery timestamp.

The fee field is always present. For payment.created and payment.processing, fee is null. For payment.completed, fee is defined once the final processing cost is known.

payment.created

{
"event": "payment.created",
"data": {
"id": "4aebf342-8e69-4712-8cbc-77326bf19660",
"idempotency_key": "f80fa916-f435-4003-a772-81b03d5c4162",
"channel_reference": "2026041200001915461001586",
"sinpe_reference": null,
"channel": "pin",
"transfer_type": "debit",
"payer": {
"iban": "CR41037010700577264928",
"document_number": "3918734189",
"document_type": 3,
"phone_number": null,
"name": "Alice Johnson"
},
"payee": {
"iban": "CR27010400128230020117",
"document_number": "0304930258",
"document_type": 0,
"phone_number": null,
"name": "Bob Smith"
},
"amount": {
"value": 10000,
"currency": "CRC",
"formatted": "100.00 CRC"
},
"fee": null,
"description": null,
"status": "pending",
"created_at": "2026-04-01 00:00:00",
"completed_at": null
},
"timestamp": "2026-04-01T00:00:00-06:00"
}

payment.processing

{
"event": "payment.processing",
"data": {
"id": "4aebf342-8e69-4712-8cbc-77326bf19660",
"idempotency_key": "f80fa916-f435-4003-a772-81b03d5c4162",
"channel_reference": "2026041200001915461001586",
"sinpe_reference": null,
"channel": "pin",
"transfer_type": "debit",
"payer": {
"iban": "CR41037010700577264928",
"document_number": "3918734189",
"document_type": 3,
"phone_number": null,
"name": "Alice Johnson"
},
"payee": {
"iban": "CR27010400128230020117",
"document_number": "0304930258",
"document_type": 0,
"phone_number": null,
"name": "Bob Smith"
},
"amount": {
"value": 10000,
"currency": "CRC",
"formatted": "100.00 CRC"
},
"fee": null,
"description": null,
"status": "processing",
"created_at": "2026-04-01 00:00:00",
"completed_at": null
},
"timestamp": "2026-04-01T00:01:00-06:00"
}

payment.completed

{
"event": "payment.completed",
"data": {
"id": "4aebf342-8e69-4712-8cbc-77326bf19660",
"idempotency_key": "f80fa916-f435-4003-a772-81b03d5c4162",
"channel_reference": "2026041200001915461001586",
"sinpe_reference": "2026041237022010000107394",
"channel": "pin",
"transfer_type": "debit",
"payer": {
"iban": "CR41037010700577264928",
"document_number": "3918734189",
"document_type": 3,
"phone_number": null,
"name": "Alice Johnson"
},
"payee": {
"iban": "CR27010400128230020117",
"document_number": "0304930258",
"document_type": 0,
"phone_number": null,
"name": "Bob Smith"
},
"amount": {
"value": 10000,
"currency": "CRC",
"formatted": "100.00 CRC"
},
"fee": {
"value": 100,
"currency": "CRC",
"formatted": "1.00 CRC"
},
"description": null,
"status": "completed",
"created_at": "2026-04-01 00:00:00",
"completed_at": "2026-04-01 00:02:00"
},
"timestamp": "2026-04-01T00:02:00-06:00"
}

payment.failed

{
"event": "payment.failed",
"data": {
"id": "80c530a1-4f79-485d-bc0d-5c560d8ff300",
"idempotency_key": "6f19de2e-f9f0-4d4b-86cd-819bcfc024f2",
"channel_reference": "2026040700001211177562391",
"sinpe_reference": null,
"channel": "pin",
"transfer_type": "debit",
"payer": {
"iban": "CR19037010600733756399",
"document_number": "3918734189",
"document_type": 3,
"phone_number": null,
"name": "Alice Johnson"
},
"payee": {
"iban": "CR09010200009097516385",
"document_number": "0303870686",
"document_type": 0,
"phone_number": null,
"name": "Bob Smith"
},
"amount": {
"value": 9900,
"currency": "CRC",
"formatted": "99.00 CRC"
},
"fee": null,
"description": null,
"status": "failed",
"created_at": "2026-04-01 00:03:00",
"completed_at": null,
"error": "Service unavailable: payment result missing from response",
"state_code": "rejected"
},
"timestamp": "2026-04-01T00:04:00-06:00"
}

transaction.received for PIN

For PIN incoming transactions, the origin account is usually identified with payer.iban. The origin payer.phone_number can be null when it is not provided by the payment rail.

{
"event": "transaction.received",
"data": {
"id": "80c530a1-4f79-485d-bc0d-5c560d8ff300",
"idempotency_key": "CREDIT_6f19de2e_f9f0_4d4b_86cd_819bcfc024f2",
"channel_reference": null,
"sinpe_reference": "2026040737022010000106803",
"channel": "pin",
"transfer_type": "credit",
"payer": {
"iban": "CR27010400128230020117",
"document_number": "0304930258",
"document_type": 0,
"phone_number": null,
"name": "Bob Smith"
},
"payee": {
"iban": "CR41037010700577264928",
"document_number": "3918734189",
"document_type": 3,
"phone_number": null,
"name": "Alice Johnson"
},
"amount": {
"value": 10000,
"currency": "CRC",
"formatted": "100.00 CRC"
},
"fee": null,
"description": "Incoming PIN payment",
"status": "completed",
"created_at": "2026-04-01 00:05:00",
"completed_at": "2026-04-01 00:05:00"
},
"timestamp": "2026-04-01T00:05:00-06:00"
}

transaction.received for SINPE Movil

For SINPE Movil incoming transactions, the origin is usually identified with payer.phone_number, and payer.iban can be null. The origin payer.phone_number can also be null when the upstream response does not provide it.

{
"event": "transaction.received",
"data": {
"id": "7b200345-c922-476e-bbc4-e2a143165ca6",
"idempotency_key": "CREDIT_f80fa916_f435_4003_a772_81b03d5c4162",
"channel_reference": null,
"sinpe_reference": "2026042800000000000000002",
"channel": "sinpe_movil",
"transfer_type": "credit",
"payer": {
"iban": null,
"document_number": "0308880719",
"document_type": null,
"phone_number": "88881234",
"name": "Bob Smith"
},
"payee": {
"iban": "CR41037010700577264928",
"document_number": "3918734189",
"document_type": 3,
"phone_number": "88880001",
"name": "Alice Johnson"
},
"amount": {
"value": 3000000,
"currency": "CRC",
"formatted": "30,000.00 CRC"
},
"fee": null,
"description": "Incoming SINPE Movil payment",
"status": "completed",
"created_at": "2026-04-01 00:06:00",
"completed_at": "2026-04-01 00:06:00"
},
"timestamp": "2026-04-01T00:06:00-06:00"
}

Handling errors

4xx responses indicate a problem with your request and are not worth retrying as-is. Fix the request first. 5xx responses indicate a problem on SELVA's side — retry these with exponential backoff.

For payment creation specifically, always retry with the same X-Idempotency-Key. SELVA will return the result of the original request rather than creating a duplicate.

See the error reference for the full list of error codes and their meanings.