POST requests to the webhook URL configured on your account.
Signatures go both ways:
- Ripio → you. Every request is signed with HMAC-SHA256, so you can check it came from Ripio.
- You → Ripio. Your answer to an approval request is signed with your own Ed25519 key, so an approval can be attributed to you and only you.
This is a separate contract from the Crypto as a Service webhooks used by the core API — the widget’s events use their own envelope and their own signatures, documented below.
Envelope
Every message shares the same envelope:Verifying the signature
Every request carries anX-Ripio-Signature-256 header: an HMAC-SHA256 signature of the raw request body, computed with the signing key issued alongside your client_id/client_secret.
transaction_approval_request
Sent before a buy executes, only if your account has transaction approval enabled. Ripio makes a single attempt with a 5-second timeout and does not follow redirects. If your endpoint doesn’t respond in time, or responds with anything other than a valid, signed approval, the operation does not execute — this request is fail-closed.
Responding
To approve, respondHTTP 200 with a JSON body that includes the event_id of the request, and sign that body (see Signing your response):
- The status is
200. - The body is a JSON object with
"approved": true— the boolean."true",1or"yes"are treated as a denial. - The body’s
event_idequals theevent_idof the request. X-Partner-Signature-Ed25519carries a valid signature of the body under the public key you registered with Ripio.
200, or a validly signed response whose approved isn’t true, denies the operation, and the user is told it wasn’t approved.
Signing your response
Your approval response is signed with an Ed25519 key pair that you generate and hold. You keep the private key; you hand the public key to Ripio during onboarding. Since only you hold the private key, a valid signature proves the approval came from your systems — an HMAC with a shared secret couldn’t prove that, because Ripio holds the same secret. Including the request’sevent_id in the signed body is what binds each signature to a single operation: without it, every {"approved": true} would be the same bytes and the same signature, reusable for any other purchase.
1. Generate the key pair
openssl or generate the pair with your language’s crypto library.
Send partner_response_public.pem (a PEM PUBLIC KEY) to your Ripio contact. Keep the private key in your secrets manager — never share it. A public key is required before transaction approval can be enabled on your account.
2. Sign the exact bytes you send
Serialize the response body once, sign those bytes, and send those same bytes. If your framework re-serializes the object after you sign it (different key order, added whitespace), the signature won’t match.
Python (cryptography)
transaction_result
Tells you how an operation — buy or sell — ended, so you can update your own record of the user’s balance and release anything you reserved when approving it. It is sent to every account with a webhook URL, whether or not transaction approval is enabled.
Each operation gets at most one result, identified by message.id, with one of two statuses:
COMPLETED— the trade executed.CANCELLED— the operation did not execute and never will. It is final: once an operation is cancelled, Ripio refuses any further attempt on it, so aCANCELLEDis never followed by aCOMPLETEDfor the sameid. If the user tries again, the widget asks for a new quote, which starts a new operation with a newid.
2xx response counts as received; anything else, or no response, is retried up to 3 times with exponential backoff (up to 60 seconds between attempts). A retry carries the same event_id — use it to discard duplicates. Your response to this message doesn’t need to be signed.
Completed:
Cancel reasons
An operation is cancelled only when it is certain that it did not execute: either Ripio rejected it, or the core was never called because your approval couldn’t be used.
Treat any other value as a cancellation too: new reasons may be added.
When you don’t receive a result
- You denied the approval. No result is sent — you already know the outcome, and you reserved nothing.
- The outcome is uncertain. If the execution fails in a way that doesn’t say whether the trade happened (a timeout or an internal error), no result is sent for now. The user can retry the same operation from the widget, and if the trade did go through, you receive its
COMPLETEDthen.
The same
id can receive more than one transaction_approval_request: when the user retries after an uncertain failure, the retry asks for your approval again, with a new event_id. Answer each one on its own merits — only the transaction_result closes the operation.For a buy, persisting
id is generally enough to reference the operation later; operation_number is what the user sees, so it’s the easiest reference if they reach out to you about it.Configuring your endpoint
Your webhook URL, HMAC signing key and Ed25519 response public key are set on your account during onboarding — reach out to your Ripio contact to configure or change them. Key considerations for your endpoint:- HTTPS. Your endpoint URL must use HTTPS and resolve to a public address. Redirects (
3xx) are not followed — point the URL at the final endpoint. - Respond quickly. For
transaction_approval_request, respond within 5 seconds — the operation is fail-closed, so a slow response means the operation doesn’t go through. - Sign your approval. Every response to
transaction_approval_requestmust carryX-Partner-Signature-Ed25519and echo the request’sevent_id. Only one public key is active per account at a time, so coordinate a key rotation with your Ripio contact. - Idempotency. Design your handler around
event_id: processing the same event twice must not double-count anything on your side.