API
17 September 2026
Changes within date version2026-09-15; the header value is unchanged and no client change is required.
Conversions
fromon unsigned transactions. Every piece of calldata ENSC returns (approvalTransaction,transactionon a voucher, andunsignedTransactionon a transfer) now names the wallet that must sign it:{ from, to, data, value: "0", chainId }.- Create finishes a
createdconversion on re-post. If a create was cut off between the insert and the voucher (a timeout, a signer or bank-rail error), the conversion is left atcreatedwithlastErrorrecorded. Re-posting the samereferencenow finishes it (HTTP 200) instead of returning the stuck row;POST /v1/conversions/{reference}/voucherdoes the same. - Reporting
failedis limited. A conversion can be reportedfailedonly before a transaction is recorded for it, and not while it is inrequires_manual_review; otherwise the answer is409 ENSC_INVALID_STATE. - Confirmation depth. A receipt must be as many blocks deep as the chain’s confirmation depth requires before
onchain_confirmedsettles. Until then,confirmedanswers409 ENSC_SETTLEMENT_VERIFICATION_FAILEDwithdetails.reasonnot_enough_confirmationsanddetails.retriabletrue. - Unexpected Naira payments go to review. A
fiat-issuepayment in a currency other than NGN, or above the amount, now parks the conversion inrequires_manual_review, as a short payment already did. - Payouts are sent once. Concurrent calls (your retry, a scheduled retry) can no longer send the same payout twice, and a payout’s status is updated only from its own bank transfer’s notifications.
- Status changes cannot overwrite each other. A conversion moves to a new status only if it is still in the status the change was based on.
- Neutral error text.
lastErrorand webhook payloads carry a neutral message instead of the bank rail’s raw error text. - Amount bounds. An amount beyond the supported range is refused with a 400 instead of failing with a 500.
- Retries run as documented. A delivery that fails (a non-2xx answer, a redirect, a timeout or a connection failure) is retried after 1 minute, 5 minutes, 15 minutes, 1 hour, 2 hours, 4 hours and 8 hours: 8 attempts in total over about 15 hours, then
gave_up. Redirects are never followed. Disabling an endpoint ends its retries. - Test deliveries carry realistic payloads.
POST /v1/webhook-endpoints/{id}/testwith aneventTypefrom the catalogue and nopayload, andPOST /v1/test-data/events, deliver the fields a real event of that type carries.GET /v1/test-data/eventslists the catalogue with a sample payload per type. - Live endpoints accept only
synthetic.test_event. A test delivery of any other type to a Live endpoint is refused withENSC_TEST_LIVE_MISMATCH, so a real event type can never be forged into a Live receiver. - Webhook scopes for restricted keys. A restricted key needs
webhooks:readto list endpoints and read events, andwebhooks:manageto create, change, test or delete endpoints. A secret key manages webhooks and reads the event log without them. - The event log is environment-scoped. An API key reads only the events of its own environment.
- Idempotency keys must be 8 to 64 characters of
A-Z a-z 0-9 _ -(400 ENSC_VALIDATION_FAILEDotherwise), are scoped to your account and environment, and are honoured on every write route, including webhook endpoints, test events and credential writes. - Request body limit. A request body may be at most 1 MiB.
- Write rate limit counts writes only. The 60-per-minute limit on the conversion, account-resolution and transfer routes no longer counts reads.
- Publishable keys are refused on every management route (credential lists, webhook endpoints, events and test data).
- Allowlists and origins. An IP allowlist CIDR entry must have a prefix of at least /8 (IPv4) or /32 (IPv6), and over-broad CORS origin patterns are refused when you register them.
- The public reference.
https://api.ensc.prosperavest.com/v1/openapi.jsonandhttps://api.ensc.prosperavest.com/v1/docsno longer list the dashboard-only credential writes. Every operation shows its@ensc/sdkcall, and the create, test delivery and test event operations document their202answers. The document also carries notes on amounts, unsigned transactions and webhooks, and documents theIdempotency-Keyalias. - Documentation corrections. The webhook retry schedule (8 attempts over about 15 hours, not 40 hours), test deliveries (queued in the endpoint’s environment, not sent to one endpoint only),
metadata(echoed on the conversion object, not in webhooks), the error codes for an unknown or retired signing key id, and the wording of the canonical request string. - Chains. Arbitrum One (
arbitrum), BNB Smart Chain (bsc) and Mode (mode), with their testnets, join the registry as token-only chains; each is served once it is enabled for an environment. - SDK.
@ensc/sdk0.4.1 carries the matching client fixes; see below.
2026-09-15
The current date version. Sent asX-ENSC-API-Version: 2026-09-15; @ensc/sdk 0.3.0 and later pin it.
- Mandatory request encryption (ENSC-ENC-V1). Every write body is an AES-256-GCM envelope
{ v, encKeyId, iv, ciphertext, tag }bound to the method, path, merchant id and key id. Plaintext merchant writes are refused withENSC_ENCRYPTION_REQUIRED. - Sealed responses (ENSC-RESP-V1). Every successful response is sealed to the merchant’s signing key with HPKE (RFC 9180 base mode: X25519, HKDF-SHA256, ChaCha20-Poly1305) and signed by ENSC. Errors are never sealed.
- Public key discovery. ENSC’s Ed25519 public keys are published at
GET /v1/.well-known/ensc-public-keys.json, for responses and webhooks. X-ENSC-Key-Idon reads. Sent on reads as well as writes, so the API knows which registered key to seal the response to.- Conversions. The API moved from the direct mint and redeem model to the ENSC converter:
POST /v1/conversionswith four types (crypto-issue,crypto-redeem,fiat-issue,fiat-redeem),GET /v1/conversions,GET /v1/conversions/{reference},GET /v1/conversions/quote,GET /v1/conversions/screening, andPOST /v1/conversions/{reference}/events,/voucherand/payout. Bank directory and account resolution:GET /v1/banks,POST /v1/accounts/resolve. Conversions run oncelo(Live) andcelo-sepolia(Sandbox). - Credential writes removed from the API. Issuing, rotating and revoking keys, and editing allowlists or origins, are dashboard actions (
ENSC_DASHBOARD_ONLY); the list operations remain. - Removed routes. The previous mint, redeem, approve, withdraw, verify-payout, virtual-account and mint-limit routes no longer exist; clients built for them receive
ENSC_NOT_FOUND.
@ensc/sdk
0.4.1
Requires API date version2026-09-15 (unchanged).
Fixed
@ensc/sdk/web3:signAndBroadcastestimates gas first (without fee fields) and sends with an explicit limit (estimate plus 30 percent, or thegasyou pass). Without a limit some nodes estimate at the block gas limit and charge that gas up front during the simulation; on Celo that empties the CELO balance the converter then pulls from, and acrypto-issuewithpair: 'CELO'failed withtransfer value exceeded balance of senderfrom any wallet holding less than a few CELO. A failed simulation now raisesENSC_UPSTREAM_FAILEDwith the node’s reason and broadcasts nothing.@ensc/sdk/web3: the helper refuses a signer that is not thefromof the transaction, an RPC endpoint on another chain, and a voucher whosewalletis not its transaction’s signer.EventDeliverynow carries every field the API returns:endpointId,responseStatus,deliveredAt,nextAttemptAt,responseSnippet; thestatusunion is documented.EventDetailgainsapiVersionanddispatchedAt.webhookEndpoints.sendTestis typed (SendTestEventResponse), and its doc comment no longer mentions a per-endpoint secret; deliveries are signed with ENSC’s published Ed25519 key.- Every
EnscErrornow carriesstatus(the HTTP status received, so a code added by the API later still classifies correctly) andrequestId(from the error body orX-ENSC-Request-Id). - An empty 2xx body is refused as
ENSC_INVALID_SIGNATURE(reason: 'empty_body'): no route this client calls answers without a sealed envelope, so a stripped body is treated like an unsigned one. - A polyfilled
fetchthat reports the timeout asAbortErroris now recognised as a timeout; a body that cannot be read is retried like a network error. - Webhook verification: the timestamp is signed as the exact header string (digits only), a byte body is hashed as received, a repeated header in a Node
IncomingHttpHeadersrecord is refused instead of thrown on, and a verified body that is not an event envelope is refused byconstructEvent(ENSC_VALIDATION_FAILED). config.baseUrlmust be https (http is allowed forlocalhostonly).- Type drift:
WebhookEndpointandCreateWebhookEndpointResponsegainapiVersion;EventSummarygainsapiVersionanddispatchedAtand itsstatusis the documentedEventOutboxStatus(created,in_flight,dispatched,abandoned);SendTestEventResponsegainsdeliveredVia;GetBalanceParamsrequiresassetorcontractAddress. - CommonJS consumers get matching
.d.ctsdeclarations (exportsnow namestypesper condition). - README: the sentence about what ENSC retains was wrong; corrected. New sections on unsigned transactions, amounts and decimals, and a complete webhooks guide.
Signer:signAndBroadcastandexecuteVoucheraccept a raw private key or any viem account (local, custody or KMS viatoAccount, or the JSON-RPC account of a connected wallet). A reverted converter call now throwsENSC_UPSTREAM_FAILEDwith the hash (details.txHash) instead of returning a result to report as confirmed; receipt errors are wrapped the same way.EnscClient.fetchPublicKeys()(also exported asfetchEnscPublicKeys): loads ENSC’s webhook keys as{ [kid]: publicKey }.verifyWebhookSignatureandconstructEventaccept that map and pick the key named byX-ENSC-Key-Id, so a key rotation needs no redeploy.ListByEnvParams:apiKeys,signingKeys,encryptionKeysandoriginslist()accept anenvfilter, as the API does.testEventsresource:list()(GET /v1/test-data/events, the catalogue with a sample payload per type) andemit({ eventType, overrides })(POST /v1/test-data/events, with a Sandbox or Live key; the events are always Sandbox events and reach only Sandbox endpoints).UnsignedTransaction.from: every piece of calldata now names the wallet that must sign it (API change of 17 September 2026).BroadcastOptions.gasandBroadcastOptions.gasMarginPercent.
0.4.0
Breaking: the API moved from the direct mint / DEX redeem model to the ENSC converter. Requires API date version2026-09-15. 0.3.x clients call routes that no longer exist and receive ENSC_NOT_FOUND.
Added
conversions:create,get,list,quote,screening,voucher,payout, andconversions.events.{submitted, confirmed, failed}. Four conversion types (crypto-issue,crypto-redeem,fiat-issue,fiat-redeem);createreturns a signed voucher with the calldata the merchant wallet signs (approvalTransaction,transaction), or bank-transferpaymentInstructionsfor a fiat-issue.banks.list()andaccounts.resolve()(bank directory and account name enquiry for the key’s environment).CONVERTER_CHAINS,ASSETS,PAIRS,Asset,Pairexports;celoandcelo-sepoliainKNOWN_CHAINS.@ensc/sdk/web3:executeVoucher(issuedVoucher, signer, { rpcUrl })(in 0.4.0 the signer was a raw private key) sends the approval then the converter call and returns both hashes;signAndBroadcastnow takes{ to, data, value, chainId }, fills gas, fees and nonce from the RPC, and returns{ txHash, status?, blockNumber? }(waitForReceipt: falsereturns as soon as the hash is known).- Conversion types re-exported:
Conversion,ConversionStatus,IssuedVoucher,PaymentInstructions,QuoteResponse, and the create parameter unions.
- License changed from MIT to Apache-2.0 (see
LICENSEandNOTICE). Earlier versions remain under MIT. balance.getacceptsasset: 'ENSC' | 'USDC' | 'USDT' | 'CELO'.transfer.createsendsasset: 'ENSC'and returns{ unsignedTransaction: { to, data, value: '0', chainId }, chain, amount }.- Unsigned transactions no longer carry
gasLimit, fees ornonce; the merchant’s signer fills them. - Dependencies:
zod4 (was 3),@noble/curves,@noble/hashesand@noble/ciphers2 (were 1). Theapischema types are zod 4 types; code that combines them with its own zod 3 schemas has to move to zod 4 as well. - The code the SDK bundles now comes from two packages published as source in the public SDK repository:
@ensc/protocol(signing, request envelope, sealed responses, errors) and@ensc/api-schemas(API types). The public exports of@ensc/sdkare unchanged. The build puts code shared by@ensc/sdkand@ensc/sdk/web3in one chunk in both module formats. - Requires Node 20.19+ or 22.12+ (was 20+). The
@noble2 packages are ESM only; the CommonJS build loads them throughrequire()of ES modules, which Node supports from those versions (Node 21 does not).
mint,redeem,approve,withdraw,verifyPayout,virtualAccounts,mintLimitresources and their parameter types.signTransactionfrom@ensc/sdk/web3(offline signing needs gas and nonce, which the API no longer supplies).- The chain slugs and asset of the previously supported network, which is no longer served.