NPCU → SenteRail Migration Guide
SenteRail (legal entity SenteRail Technologies Company Limited, formerly trading as NPCU — National Payments Corporation of Uganda) is renaming the brand and the technical identifiers that go with it. This guide is the single reference for integrators upgrading from NPCU-prefixed identifiers to SenteRail-prefixed identifiers.
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 | "NPCU", "National Payments Corporation of Uganda" | "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 rebrand has Uganda regulatory and contractual exposure that must be cleared before public flip. 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 — company name change continuity NPCU Ltd → SenteRail Technologies Company Limited, or new incorporation with business-transfer documentation.
- 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 URSB name-change continuity evidence is captured.
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-0023 Domain cutover NPCU → SenteRail
- Audit doc:
docs/migrations/npcu-to-senterail/audit.md - Legal evidence pack:
docs/legal/senterail-name-change-evidence.md