Legacy Identifier Migration Reference
SenteRail is the canonical current brand and product name. This page is a historical support reference for integrators and operators removing legacy NPCU-prefixed identifiers from older deployments.
Use this guide only when you encounter a legacy API key, webhook header, environment variable, host literal, plugin slug, or storage key that still uses the old prefix. New integrations should start from the current SenteRail developer documentation instead of this migration page.
Hard cutover
This is a hard cutover, not a soft deprecation. On the cutover day:
- Every live merchant API key (
npcu_live_*) and test key (npcu_test_*) stops parsing instantly. - Every webhook verifier reading
X-NPCU-Signaturestops validating instantly. Merchants must redeploy verifier code keyed onX-SenteRail-Signature. - Every
NPCU_*andVITE_NPCU_*environment variable read stops resolving — CI/CD systems must rename secrets in lockstep.
There is no compatibility window. Plan your rollout to land all four changes at the same time.
Effective date
- Cutover day: <TBD — product to confirm>.
- Merchant notice: a minimum of 30 days written notice is required before the cutover under standard PSP terms. The cutover date will be communicated on the merchant portal and via the merchant-of-record email on file no fewer than 30 days before it lands.
What changes
| Surface | Before (NPCU) | After (SenteRail) | Action |
|---|---|---|---|
| Brand / display | Legacy brand copy | "SenteRail" | Update merchant-facing copy. |
| Legal entity | NPCU Ltd (or trading name) | SenteRail Technologies Company Limited | Update procurement/finance records. |
| Apex domain | npcu.co.ug, *.npcu.co.ug, *.npcu.ug | senterail.com, *.senterail.com | Re-point integrations; paths unchanged. |
| Backend env vars | NPCU_* | SENTERAIL_* | Rename in Doppler / Vault / CI. |
| Frontend env vars | VITE_NPCU_* | VITE_SENTERAIL_* | Rename in build env. |
| HTTP webhook headers | X-NPCU-* | X-SenteRail-* | Redeploy verifier code. |
| gRPC metadata | x-npcu-* | x-senterail-* | Internal — co-deployed by SenteRail. |
| API key prefix | npcu_test_* / npcu_live_* | srail_test_* / srail_live_* | Rotate via merchant portal. |
Endpoint mapping
Paths are unchanged. Only the host changes.
| Surface | Before | After |
|---|---|---|
| Public API | https://api.npcu.co.ug | https://api.senterail.com |
| Sandbox API | https://api.sandbox.npcu.co.ug | https://api.sandbox.senterail.com |
| Developer docs | https://docs.npcu.co.ug | https://docs.senterail.com |
| Hosted checkout | https://pay.npcu.co.ug (or pay.npcu.ug) | https://pay.senterail.com |
| Checkout SDK | https://js.pay.npcu.co.ug | https://js.pay.senterail.com |
| Status page | https://status.npcu.co.ug | https://status.senterail.com |
| Operator app | https://app.npcu.co.ug | https://app.senterail.com |
| Auth sandbox | https://auth.sandbox.npcu.co.ug | https://auth.sandbox.senterail.com |
| Console sandbox | https://console.sandbox.npcu.co.ug | https://console.sandbox.senterail.com |
The internal operator alias ops.senterail.com is retained for back-office tooling.
Webhook header mapping
The five outbound HTTP headers on every signed webhook delivery rename in lockstep. Algorithm (HMAC-SHA256 over ${t}.${body} where t is the timestamp parsed from the signature header) is unchanged.
| Before | After |
|---|---|
X-NPCU-Signature | X-SenteRail-Signature |
X-NPCU-Event-Id | X-SenteRail-Event-Id |
X-NPCU-Delivery-Id | X-SenteRail-Delivery-Id |
X-NPCU-Event-Type | X-SenteRail-Event-Type |
X-NPCU-Api-Version | X-SenteRail-Api-Version |
Internal gRPC metadata keys also rename (x-npcu-request-id, x-npcu-correlation-id, x-npcu-partner-id, x-npcu-sacco-id, x-npcu-merchant-id, x-npcu-cell-id, x-npcu-actor-type, x-npcu-actor-id, x-npcu-actor-scopes, x-npcu-ai-mode, x-npcu-idempotency-key → x-senterail-*). These are server-to-server and ship co-deployed; no integrator action.
API key prefix mapping
| Before | After |
|---|---|
npcu_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | srail_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
npcu_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | srail_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
Existing keys must be rotated. Old npcu_* prefixes are no longer parsed by the auth middleware after the cutover.
How to rotate:
- Sign in to the merchant portal at
https://senterail.com/sign-in/merchant. - Open Developers → API keys.
- For each active key, click Rotate and copy the new
srail_*key. - Deploy the new key to every environment that previously held the legacy key (production, staging, CI, plugin settings).
- Revoke the legacy key once your rollout is verified.
Backend environment variable mapping
Every NPCU_* backend environment variable renames to SENTERAIL_*. The value semantics are unchanged. Renames apply to:
- All runtime, security, session, API key, webhook secret, callback, settlement, reconciliation, ledger, SMTP, and bootstrap variables.
- Mock provider flags (
NPCU_MTN_USE_MOCK→SENTERAIL_MTN_USE_MOCK, etc.). - Step-up authentication flags (
NPCU_STEP_UP_PII→SENTERAIL_STEP_UP_PII).
The exhaustive list lives in api/internal/config/config.go after the cutover; the current list is mirrored in the Configuration Reference.
Frontend environment variable mapping
| Before | After |
|---|---|
VITE_NPCU_API_BASE_URL | VITE_SENTERAIL_API_BASE_URL |
VITE_NPCU_API_BASE_URL_SANDBOX | VITE_SENTERAIL_API_BASE_URL_SANDBOX |
VITE_NPCU_INTERNAL_HOSTS | VITE_SENTERAIL_INTERNAL_HOSTS |
VITE_NPCU_ENV | VITE_SENTERAIL_ENV |
VITE_NPCU_FORCE_MOCKS | VITE_SENTERAIL_FORCE_MOCKS |
VITE_NPCU_FLAGS | VITE_SENTERAIL_FLAGS |
VITE_NPCU_FLAGS_OFF | VITE_SENTERAIL_FLAGS_OFF |
Webhook verifier code samples
The signature scheme is unchanged: ${t}.${body} HMAC-SHA256 with the merchant-provided signing secret, base64 encoded, sent in the X-SenteRail-Signature header as t=<unix-seconds>,v1=<base64-mac>.
JavaScript / Node
js
import crypto from 'node:crypto'
export function verifySenteRailSignature(rawBody, header, secret) {
if (!header) return false
const parts = Object.fromEntries(header.split(',').map(p => p.trim().split('=')))
const t = parts.t
const v1 = parts.v1
if (!t || !v1) return false
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('base64')
const a = Buffer.from(expected)
const b = Buffer.from(v1)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
// usage with express raw body:
// const ok = verifySenteRailSignature(req.rawBody, req.get('X-SenteRail-Signature'), SECRET)PHP
php
function verify_senterail_signature(string $rawBody, ?string $header, string $secret): bool {
if (!$header) return false;
$parts = [];
foreach (explode(',', $header) as $kv) {
$kv = trim($kv);
[$k, $v] = array_pad(explode('=', $kv, 2), 2, null);
if ($k !== null && $v !== null) {
$parts[$k] = $v;
}
}
if (empty($parts['t']) || empty($parts['v1'])) return false;
$expected = base64_encode(hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret, true));
return hash_equals($expected, $parts['v1']);
}Go
go
package webhook
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"net/http"
"strings"
)
func VerifySenteRailSignature(rawBody []byte, r *http.Request, secret []byte) bool {
header := r.Header.Get("X-SenteRail-Signature")
if header == "" {
return false
}
var t, v1 string
for _, part := range strings.Split(header, ",") {
kv := strings.SplitN(strings.TrimSpace(part), "=", 2)
if len(kv) != 2 {
continue
}
switch kv[0] {
case "t":
t = kv[1]
case "v1":
v1 = kv[1]
}
}
if t == "" || v1 == "" {
return false
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(t + "." + string(rawBody)))
expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(v1))
}Python
python
import base64
import hashlib
import hmac
def verify_senterail_signature(raw_body: bytes, header: str | None, secret: bytes) -> bool:
if not header:
return False
parts = {}
for kv in header.split(","):
kv = kv.strip()
if "=" in kv:
k, v = kv.split("=", 1)
parts[k] = v
t = parts.get("t")
v1 = parts.get("v1")
if not t or not v1:
return False
mac = hmac.new(secret, f"{t}.{raw_body.decode('utf-8')}".encode("utf-8"), hashlib.sha256)
expected = base64.b64encode(mac.digest()).decode("ascii")
return hmac.compare_digest(expected, v1)Plugin upgrade notes
For all commerce platform plugins (WooCommerce, WHMCS, Shopify, Wix, Magento, OpenCart, PrestaShop, Bagisto):
- Display name, icon, and asset paths change to SenteRail brand.
- Default API host changes to
api.senterail.comand checkout host topay.senterail.com. - Storage keys are preserved. Your existing settings, gateway IDs, option keys, order meta, action/filter hook names, module slugs, and config-path keys continue to work after the upgrade. You do not need to migrate stored data.
- You must install a rotated API key. The plugin settings UI will prompt for the new
srail_*key on first run after upgrade.
Per-plugin specifics:
| Plugin | Display name | Default API host | Storage / handle preserved |
|---|---|---|---|
| WooCommerce | "SenteRail Pay" | api.senterail.com | gateway id npcu_checkout, option key woocommerce_npcu_checkout_settings, order meta _npcu_session_id, _npcu_payment_id, _npcu_idempotency_key, _npcu_webhook_event_ids, _npcu_payment_rail, action/filter hook names |
| WHMCS | "SenteRail Pay" | api.senterail.com | module slug npcuug, function names npcuug_* |
| Shopify | "SenteRail Pay" | api.senterail.com | webhook topic subs, app handle if already published |
| Wix | "SenteRail Pay" | api.senterail.com | integrationType, app ID |
| Magento | "SenteRail Pay" | api.senterail.com | module name NPCU_Checkout, config_path keys |
| OpenCart | "SenteRail Pay" | api.senterail.com | folder/class names, install hooks |
| PrestaShop | "SenteRail Pay" | api.senterail.com | folder/class names, install hooks |
| Bagisto | "SenteRail Pay" | api.senterail.com | folder/class names, install hooks |
Rollout checklist
Before the cutover date:
- [ ] Rotate at least one API key per environment and confirm the new
srail_*key parses end-to-end against the sandbox. - [ ] Redeploy webhook verifier code reading
X-SenteRail-Signature(the legacy header stops being emitted on cutover day). - [ ] Rename
NPCU_*/VITE_NPCU_*secrets in your CI, Doppler/Vault, and runtime config. - [ ] Re-point any hard-coded host literals (
api.npcu.co.ug,pay.npcu.*,js.pay.npcu.*) to*.senterail.com. - [ ] If you operate plugins, run the new plugin version against the sandbox with rotated keys before moving to production.
- [ ] Confirm your monitoring dashboards alert on
X-SenteRail-Signaturefailures rather thanX-NPCU-Signaturefailures.
Sandra Nabossa Legal Review
This historical identifier cutover has Uganda regulatory and contractual exposure that must be cleared before any public claim relies on it. The items below are tracked in docs/legal/senterail-name-change-evidence.md; each remains a Sandra-flagged item until official-source evidence is captured.
Conservative-language scrub
Under SenteRail, the following claims are not made on this site until evidence is captured in docs/legal/senterail-name-change-evidence.md:
- "regulated by Bank of Uganda" / "BoU regulated"
- "authorized sandbox participant" / "supervised in the BoU regulatory sandbox"
- "licensed payment provider" / "pursuing full licensing"
- "PDPO registered" / "registered Data Controller"
- "appointed Data Protection Officer"
- "national" / "official"
- Specific UMRA / FIA / NIRA / URA registration claims
When discussing regulatory engagement, SenteRail uses descriptive language:
- "engages with the Bank of Uganda under the National Payment Systems Act 2020" (not "regulated by").
- "aligns its data handling with the Data Protection and Privacy Act 2019" (not "PDPO registered").
- "renders UMRA-aligned report templates from the ledger" (not "produces UMRA returns automatically").
Open verification list
Each item below is open until official-source evidence is filed:
- URSB — legacy-entity continuity or new-incorporation / business-transfer documentation for SenteRail Technologies Company Limited.
- BoU — current PSP / payment-system status under new legal name; sandbox status if applicable.
- FIA — accountable-person filing under new name.
- PDPO / NITA-U — controller/processor registration carried over or refiled.
- URA — TIN/taxpayer name update.
- Bank settlement accounts — name change on each collection / disbursement account.
- Provider contracts — MTN MoMo, Airtel Money, Mastercard (Move + Gateway), Jenga, EFRIS, identity providers — addendums or novation under the new legal name.
- Merchant notices — the hard-cutover decision requires ≥30 days written notice to merchants of brand and legal-entity change and the API-key / webhook-header break.
- Privacy policy, DPA, ToS, AML/KYC — re-execution under new name.
- Trademark filing — "SenteRail" word + logo at URSB (Ugandan trademark register); "rails" usage cleared against EAC market.
Hard-cutover-specific blockers
- Mass merchant API key rotation event — under what authority, with what notice, and on what day?
- Use of word "rails" in product positioning — trademark conflict check in EAC market.
- Retention of
assets/NPCU-Brand-Assets/until Sandra confirms the legacy evidence trail is no longer required.
Sandra Nabossa is a legal review persona, not a licensed advocate's formal opinion. Every regulatory claim above is flagged "to be verified from official source" until evidence is filed in
docs/legal/senterail-name-change-evidence.md.
Related references
- Architecture decision: ADR-0024 Domain and identifier cutover
- Audit doc:
docs/archive/npcu-to-senterail-migration/audit.md - Legal evidence pack:
docs/legal/senterail-name-change-evidence.md