signalapp/Signal-Server · error · IOException
Registration service failure
Error message
Registration service failure
What it means
PhoneVerificationTokenManager wraps calls to the external registration service (gRPC). When the remote verify call fails with a gRPC status other than INVALID_ARGUMENT (e.g. UNAVAILABLE, DEADLINE_EXCEEDED, INTERNAL), the error is logged and rethrown as an IOException so callers can treat it as a transient backend failure rather than a client mistake.
Solutions
- Check registration service health/availability and its logs for the correlation error logged alongside this message.
- Verify the registration service URI/credentials in the service configuration and network connectivity/DNS from this host.
- Retry the request after the transient condition clears; wrap the IOException in retry-with-backoff logic at the resource layer.
- Confirm the gRPC call's deadline is generous enough for production latency.
Example fix
// before
try {
phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
return Response.status(503).build();
}
// after
try {
phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
logger.warn("registration service transient failure, retrying", e);
return retryWithBackoff(() -> phoneVerificationTokenManager.verify(...));
} Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
try {
phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
if (e.getCause() instanceof StatusRuntimeException sre && isRetryable(sre.getStatus().getCode())) {
scheduleRetryWithBackoff();
} else {
return Response.status(503).build();
}
} Prevention
- Monitor registration service health and alert on elevated gRPC error rates
- Use retry with exponential backoff for IOException from verification paths
- Keep gRPC deadlines generous but bounded
- Log the cause StatusRuntimeException for diagnosability
When it happens
Trigger: Calling PhoneVerificationTokenManager.verify() -> verifyBySessionId() with a valid session id while the registration service is down, unreachable, times out, or returns an unexpected gRPC error code.
Common situations: Registration service outage or deployment; network partition between Signal service and registration service; gRPC deadline exceeded under load; misconfigured registration service endpoint in production configuration.
Related errors
- registration service unavailable
- 503 Service Unavailable
- 500 Internal Server Error (registration service failure)
- value is not valid base64 url
- value is not in E164 format
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/8936b9c054a1eb73.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/PhoneVerificationTokenManager.java:110
throws UnverifiedRegistrationSessionException, InvalidRegistrationSessionException, IOException {
try {
final RegistrationServiceSession session = registrationServiceClient
.getSession(sessionId, REGISTRATION_RPC_TIMEOUT)
.orElseThrow(UnverifiedRegistrationSessionException::new);
if (!MessageDigest.isEqual(number.getBytes(), session.number().getBytes())) {
throw new InvalidRegistrationSessionException("number does not match session");
}
if (!session.verified()) {
throw new UnverifiedRegistrationSessionException();
}
} catch (final StatusRuntimeException e) {
if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) {
throw new InvalidRegistrationSessionException(e.getMessage());
}
logger.error("Registration service failure", e);
throw new IOException("Registration service failure", e);
}
}
private void verifyByRecoveryPassword(
final String number,
@Nullable final String userAgent,
@Nullable final String acceptLanguage,
@Nullable final String mostRecentProxy,
final byte[] recoveryPassword)
throws InterruptedException, IOException, RecoveryPasswordVerificationFailedException {
if (!registrationRecoveryChecker.checkRegistrationRecoveryAttempt(number, userAgent, acceptLanguage, mostRecentProxy)) {
throw new RecoveryPasswordVerificationFailedException();
}
try {
final UUID phoneNumberIdentifier = phoneNumberIdentifiers.getPhoneNumberIdentifier(number)
.get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);View on GitHub (pinned to 100ab61c82)