System information
Technical overview of this deployment—provider requirements, server-to-server data relay, the embed generator, webhook signing, and JSON APIs.
On this page
Overview
CorePay is a SvelteKit site that sends payers to configured providers (exchanges and similar integrations), can register a signed webhook per payment link (stored until the provider confirms), and exposes small JSON APIs for listings and server-to-server relay.
Integrators typically build links or forms that hit GET /link, use GET /api/v1/providers for UI lists, and verify webhook bodies using /.well-known/jwks.json.
Stack and configuration
The app uses the MOTA ĐApp stack (SvelteKit). Locales and navigation come from site config (e.g. vite-plugin-config); payment providers are defined in src/data/providers.ts (ids, URLs, optional dataUrl, optional refCode, optional remapKeys, suspension, expiration).
The payment link handler lives under src/lib/server/link; optional wh + data on GET /link are persisted in Cloudflare D1 using the Workers binding named by env DB_BINDING_NAME. Apply SQL from _migrations/ on that database. POST /api/v1/data from the provider composes the signed webhook body and delivers it to the stored URL.
Provider requirements
Expectations for payment providers integrating with CorePay: provide a payment link that prefills transfer details in your portal, accept our payment ID, confirm settlements to us, and give us a referral code for users we send.
| Requirement | Description |
|---|---|
| Payment link | Create a payment link whose role is to prefill transfer information when the payer opens your portal. |
| Payment ID variable | Integrate the payment ID variable we send you—use it in your flow and keep it consistent end to end. |
| Webhook after successful transfer | After you know the payment state, POST JSON to our /api/v1/data with paymentId, amount, currency, status (sent or completed), addressTo, and addressFrom (and optional boolean swap if you changed the asset) so we can deliver the signed webhook to the merchant’s registered URL. |
| Referral code | Create a referral code for us so users we send are attributed correctly. |
Provider data relay — POST /api/v1/data
Called by the provider’s servers (not the payer’s browser). Body must be a JSON object with only the required webhook keys in the table above, optional swap (boolean), and no customData or wh—the target URL was set on the original GET /link.
If no row exists for paymentId, the response is still HTTP 200 with "ok", "wh". If a row exists, CorePay signs and POSTs to the registered wh; the response is "ok", "wh" after attempts complete. If signing is not configured, env DB_BINDING_NAME is unset, or D1 is unavailable, you may receive 503.
Payment embed generator
The /generator page builds a static HTML form (no JavaScript) whose action is this site’s /link URL. Fields follow the same rules as the payment link gateway (currency required). Use your production origin in the snippet when going live. /generator
Payment link composition (GET /link)
This flow is a cleaned payment link: CorePay normalizes your parameters and forwards the payer straight to the provider’s payment app. It is not the same as that provider’s own merchant or marketing link—those are separate products.
You call GET /link on your CorePay origin. The gateway validates the query, resolves provider and payee, then responds with HTTP 302 to the provider’s configured payment URL (paymentUrl or url in providers.ts). When wh is set, CorePay validates it and optional data, stores them in D1 keyed by the new payment-id, and never puts wh or data on the redirect URL.
The embed generator uses the parameter names in the first table. The orange preview opens the current values in a new tab. The compact address book (and any internal portal selector on multi-provider embeds) exists only on the CorePay request. On GET /link you may use the provider parameter as a synonym for portal; neither is sent to the payment app. On the provider URL, the same name address means a single payee id (the resolved receiver)—not the comma-separated book.
Parameters on the CorePay /link request
| Parameter | Required / optional | Description |
|---|---|---|
| address | Required | Required on GET /link only: comma-separated providerId:receiver compact book. CorePay resolves it to one provider and one payee id. On the redirect, that id is sent as a single address= query value (not the whole book string). Every provider id must be registered and active here. |
| currency | Required | Receiving asset code (lowercased by the server). Letters, digits, “.”, “_”, and “-” only. |
| portal | Optional | When address lists multiple providerId:receiver pairs, required: that provider id selects the payee row. With a single pair, optional (if present it must match that id). CorePay-only; not sent on the redirect. |
| provider | Optional | Synonym for portal (same rules). Send only one of portal or provider per request. CorePay-only; never forwarded to the payment app. |
| lang | Optional | UI / payment-app language; must be a locale enabled on this site. Omit to default to en. |
| amount | Optional | Optional positive decimal amount forwarded to the payment app. |
| digitize | Optional | Optional. When true, 1, or yes, forwarded as digitize=true so the provider may offer digitized alternatives (USDX, USDC, RUBX, …) when the corresponding fiat is unavailable. Preference is set by the exchange or your settings. When false, 0, no, or omitted, the parameter is not sent—providers treat absence as false. |
| donate | Optional | Send donate=1 to mark a donation flow when the provider supports it. |
| rc | Optional | Optional recurring pattern (e.g. d, m, 3d, 2w) validated by the gateway. |
| fiat | Optional | Optional fiat quote ticker (lowercased when forwarded). |
| dl | Optional | Optional deadline: values ≤400 are treated as minutes from now; larger values as Unix time (seconds if <1e12, else ms). Past deadlines are rejected. |
| swap | Optional | Optional delivered-currency hint for the payment app. |
| org | Optional | Optional organization label, at most 25 Unicode characters. |
| item | Optional | Optional item or purpose label, at most 40 Unicode characters. |
| photo | Optional | Optional merchant image URL (https, ipfs, or http on localhost). If the payment app only accepts https, replace ipfs:// with the gateway https://ipf.sk/ in the same path layout (e.g. ipfs://bafy…/file → https://ipf.sk/ipfs/bafy…/file). |
| item-photo | Optional | Optional item image URL (same URL rules as photo), including rewriting ipfs:// to https://ipf.sk/ when you need https. |
| wh | Optional | Optional webhook URL (secure https website only; no localhost or IP-literal targets). If set, CorePay validates it, stores it in D1 with the generated payment-id, and does not append it to the provider redirect. Delivery happens later when your backend POSTs payment details to /api/v1/data. |
| data | Optional | Optional JSON object (URL-encoded); only allowed when wh is set. Canonicalized (keys sorted, minified) before storage. Not sent on the provider payment URL. Becomes customData on the outgoing webhook when present. |
Example GET /link (CorePay gateway)
https://corepay.money/link?address=ping%3AMERCHANT123¤cy=xcb&lang=en This hits your CorePay deployment first. The payer is then redirected to the provider’s own host and path.
What the end provider’s payment URL receives
The browser follows the redirect to the provider’s real payment entry—the environment where the payer completes the transfer. CorePay sends address= (resolved payee id), currency= (lowercased), and payment-id (new for each redirect). ref-code is included when providers.ts sets refCode. lang= is included when the resolved UI language is not English; omitted for English. Each optional row below is sent only when present and valid on GET /link. The data and wh parameters are never sent to the provider. The compact book, portal, and provider selector parameters never appear on this URL.
Optional per-provider remapKeys in providers.ts renames any default query key (e.g. ref-code → affiliate, payment-id → txn, amount → amt) for that provider’s payment app. Keys in the map are CorePay’s canonical names; omitted keys keep the default. The same applies to every optional forwarded parameter in the table.
| Parameter | Required / optional | Description |
|---|---|---|
| address | Required | Single payee id: the receiver from your book for the selected provider (the value after providerId: in that row). The name address matches GET /link, but here it is one id, not the comma-separated book. The payment app reads it from the address query parameter. |
| currency | Required | Lowercased currency from the request. |
| payment-id | Required | Required. Generated by CorePay for this redirect: UUID v4 with hyphens removed, lowercase hexadecimal (e.g. a1b2c3d4e5f6789012345678abcdef). Use it in webhooks and reconciliation; remapKeys may rename the query key. |
| ref-code | Optional | Optional when refCode is set in providers.ts for this provider; sent as ref-code unless remapKeys renames it. |
| lang | Optional | Omitted when the payment language is English (the default). Present as lang= when the request resolves to a non-English locale enabled on this site. |
| amount | Optional | Optional. Included when sent on GET /link and valid: positive decimal amount. remapKeys may rename the query key. |
| digitize | Optional | Optional. Included only when GET /link sends a truthy digitize (true, 1, or yes); value is true. Omitted when false or absent on the gateway request. remapKeys may rename the query key. |
| dl | Optional | Optional. Included when sent on GET /link and valid: deadline (≤400 = minutes from now; larger = Unix time in seconds if <1e12, else ms). Past deadlines are rejected on the gateway. |
| fiat | Optional | Optional. Included when sent on GET /link; value is lowercased on the outbound URL. |
| donate | Optional | Optional. Included when donate=1 or true on GET /link. |
| rc | Optional | Optional. Included when sent on GET /link and valid recurring pattern (e.g. d, m, 3d, 2w), lowercased. |
| swap | Optional | Optional. Included when sent on GET /link (delivered-currency hint for the payment app). |
| org | Optional | Optional. Included when sent on GET /link and valid: organization label, at most 25 Unicode characters. |
| item | Optional | Optional. Included when sent on GET /link and valid: item or purpose label, at most 40 Unicode characters. |
| photo | Optional | Optional. Included when sent on GET /link and valid: merchant image URL (https, ipfs, or http on localhost). For https-only UIs, rewrite ipfs:// to https://ipf.sk/ keeping the equivalent path (e.g. via /ipfs/…). |
| item-photo | Optional | Optional. Included when sent on GET /link and valid: item image URL (same rules as photo), including ipfs:// → https://ipf.sk/ when required. |
Example URL at the provider (illustrative)
https://provider-pay.example/path?address=MERCHANT123¤cy=xcb&payment-id=a1b2c3d4e5f6789012345678abcdef&ref-code=spjSUXQo The payer’s browser loads this URL at the end provider. If you already depend on a different payment-link layout or the provider only documents a fixed link format, contact us for customization.
Webhooks and X-Signature
When a payer opens GET /link with wh, CorePay stores the webhook URL and optional data in D1 under the generated payment-id, then redirects the browser to the provider. No POST to your server happens at redirect time.
The provider’s backend calls POST /api/v1/data with the required keys in the table below and may add optional swap (boolean)—unknown keys are rejected with 400. CorePay merges them with optional customData from the link, canonicalizes the full object (sorted keys, minified), signs it, and POSTs it to the registered wh with Content-Type: application/json.
JSON body POSTed to your webhook URL
| JSON key | Required / optional | Description |
|---|---|---|
| paymentId | Required | Same value as payment-id on the payment link: UUID v4 without hyphens, 32 lowercase hex characters. Use for reconciliation. |
| amount | Required | Decimal amount as a string (e.g. "100" or "3.14") or JSON number; normalized to a decimal string in the outgoing body. When swap is true, this is the amount in the delivered (post-swap) currency. |
| currency | Required | Asset code; lowercased by CorePay. Letters, digits, “.”, “_”, and “-” only. When swap is true, this is the delivered asset after the provider’s swap. |
| swap | Optional | Optional boolean from the provider’s POST /api/v1/data. true means the provider swapped to a different asset than the payer originally requested; amount and currency are then the post-swap values actually delivered. Omit or false when no swap occurred. |
| status | Required | sent — user sent or submitted the transfer at the exchange (in-flight). completed — provider reports funds credited to the merchant account. |
| addressTo | Required | Receiving-side identifier (e.g. merchant account or deposit id) as reported by the provider. |
| addressFrom | Required | Sending-side identifier (e.g. user wallet or exchange account id) as reported by the provider. |
| customData | Optional | Present only if the payer’s GET /link included valid data. Canonical JSON object from the link (keys sorted, minified when stored); the full webhook payload is canonicalized again at send time. |
Header X-Signature: detached compact JWS (JOSE, EdDSA / Ed25519, RFC 7797 b64:false) over the exact UTF-8 bytes of the JSON body—form protected..signature (two dots).
Delivery uses up to three attempts (immediate, then after 10s, then after 60s). Verify signatures using the keys in /.well-known/jwks.json.
Example JSON POST body your webhook receives (customData only if the payer’s link included data):
{"addressFrom":"user_wallet_or_id","addressTo":"merchant_recv_id","amount":"100","currency":"xcb","customData":{"orderId":"42"},"paymentId":"a1b2c3d4e5f6789012345678abcdef","status":"completed","swap":false}REST API — /api/v1/…
JSON responses. OPTIONS is supported; responses include CORS headers (e.g. Access-Control-Allow-Origin: *) and X-APP-Version. Replace the version segment if your deployment uses another API version folder.
- GET /api/v1/providers — JSON object with a providers array of entries (id, title, url, icon) for each active provider (not suspended, not past expiration).
- POST /api/v1/data — provider backend reports a payment: strict JSON with required paymentId, amount, currency, status (sent or completed), addressTo, addressFrom, plus optional boolean
swap. CorePay looks up the payment-id in D1; if missing, returns HTTP 200 with JSON body "ok", "wh". If found, builds canonical JSON (plus optional customData from the link), signs it, POSTs to the stored wh with X-Signature, retries up to three times, then returns HTTP 200 with "ok", "wh". Max body 64 KiB. - GET /api/v1/health — JSON with status, timestamp, environment, and version fields for uptime checks.
- GET /api/v1/ping — simple JSON with status, message, and timestamp.
API responses may include the X-APP-Version header (from VITE_APP_VERSION at build time).