Notarization Guide
Comprehensive guide to Apple notarization for macOS apps.
What is Notarization?
Notarization is Apple's automated scanning process that checks your app for malicious content and code-signing issues. Once notarized, macOS Gatekeeper allows the app to run without showing security warnings.
Without notarization: Users see "malicious software" warnings With notarization: App opens normally without warnings
Prerequisites
1. Apple ID Credentials
You need three pieces of information:
- Apple ID email - Your developer account email
- Team ID - Found at https://developer.apple.com/account (Membership → Team ID)
- App-specific password - Generate at appleid.apple.com
2. Generate App-Specific Password
- Go to https://appleid.apple.com
- Sign in with your Apple ID
- Navigate to "Sign-In and Security"
- Click "App-Specific Passwords"
- Click "Generate an app-specific password"
- Name it (e.g., "Notarization Tool")
- Copy the generated password (format:
xxxx-xxxx-xxxx-xxxx)
Important: Save this password securely. You won't be able to see it again.
3. Store Credentials (Optional)
You can store credentials in your keychain for repeated use:
xcrun notarytool store-credentials "notarytool-profile" \
--apple-id "your-email@example.com" \
--team-id "YOUR_TEAM_ID" \
--password "xxxx-xxxx-xxxx-xxxx"Then use the profile for future submissions:
xcrun notarytool submit APP.dmg --keychain-profile "notarytool-profile" --waitAlternative: App Store Connect API Key
For CI/CD or when you prefer API keys over app-specific passwords:
Generate API Key:
- Go to https://appstoreconnect.apple.com/access/api
- Create a new key with "Developer" role
- Download the
.p8file (only available once) - Note the Key ID and Issuer ID
Store the key:
mkdir -p ~/private_keys mv AuthKey_KEYID.p8 ~/private_keys/Submit using API key:
xcrun notarytool submit APP.dmg \ --key ~/private_keys/AuthKey_KEYID.p8 \ --key-id YOUR_KEY_ID \ --issuer YOUR_ISSUER_ID \ --wait
Advantages of API keys:
- No password expiration
- Better for automation/CI
- More granular access control
Notarization Workflow
Step 1: Submit for Notarization
Submit your DMG or app for notarization:
xcrun notarytool submit ~/Desktop/APP-VERSION.dmg \
--apple-id your-email@example.com \
--team-id YOUR_TEAM_ID \
--password xxxx-xxxx-xxxx-xxxx \
--waitFlags:
--wait: Wait for processing to complete (recommended)- Without
--wait: Returns immediately with submission ID
Expected output:
Conducting pre-submission checks for APP.dmg...
Submission ID received
id: 12345678-1234-1234-1234-123456789abc
Successfully uploaded file
Waiting for processing to complete.
Current status: In Progress......
Processing complete
id: 12345678-1234-1234-1234-123456789abc
status: AcceptedProcessing time: Usually 1-3 minutes
Step 2: Check Submission Status
If you didn't use --wait, check status manually:
xcrun notarytool info SUBMISSION_ID \
--apple-id your-email@example.com \
--team-id YOUR_TEAM_ID \
--password xxxx-xxxx-xxxx-xxxxStep 3: View Notarization Log
If notarization fails (status: "Invalid"), get detailed logs:
xcrun notarytool log SUBMISSION_ID \
--apple-id your-email@example.com \
--team-id YOUR_TEAM_ID \
--password xxxx-xxxx-xxxx-xxxxLogs are returned as JSON with specific error details.
Step 4: Staple Notarization Ticket
Once accepted, staple the ticket to your DMG:
xcrun stapler staple ~/Desktop/APP-VERSION.dmgOutput:
Processing: /Users/you/Desktop/APP-VERSION.dmg
The staple and validate action worked!What stapling does:
- Attaches the notarization ticket to the DMG
- Allows the app to be verified offline
- Required for distribution outside the App Store
Verifying Notarization
Check Notarization Status
spctl -a -vvv /path/to/APP.appNotarized app output:
/path/to/APP.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: Your Name (TEAM_ID)Non-notarized app output:
/path/to/APP.app: rejected
source=Unnotarized Developer IDCheck Code Signing
codesign -dvvv /path/to/APP.appLook for:
Authority=Developer ID Application: Your Name (TEAM_ID)Signature size=(should be present)TeamIdentifier=YOUR_TEAM_ID
Check Stapled Ticket
stapler validate /path/to/APP.dmgOutput if stapled:
Processing: /path/to/APP.dmg
The validate action worked!Common Notarization Errors
Error: Invalid Credentials (HTTP 401)
Symptom:
Error: HTTP status code: 401. Invalid credentials.Causes:
- Wrong Apple ID email
- Expired or incorrect app-specific password
- Wrong team ID
Solution:
- Verify Apple ID email is correct
- Generate a new app-specific password
- Confirm team ID at https://developer.apple.com/account
Error: Archive Contains Critical Validation Errors
Symptom:
status: Invalid
statusSummary: Archive contains critical validation errorsCommon causes:
- Unsigned frameworks: Embedded frameworks not properly signed
- Missing timestamps: Signatures don't include secure timestamps
- Wrong certificate: Not signed with "Developer ID Application"
Solution:
Use proper export with xcodebuild -exportArchive instead of copying from archive:
xcodebuild -exportArchive \
-archivePath ~/Desktop/APP.xcarchive \
-exportPath ~/Desktop/APP-Export \
-exportOptionsPlist ExportOptions.plistThis ensures all embedded frameworks (like Sparkle) are properly signed.
Error: Binary Not Signed with Valid Developer ID
Symptom in logs:
{
"severity": "error",
"message": "The binary is not signed with a valid Developer ID certificate."
}Solution:
Verify certificate is installed:
security find-identity -v -p codesigning | grep "Developer ID Application"Re-export the app with proper signing options
Error: Signature Does Not Include Secure Timestamp
Symptom in logs:
{
"severity": "error",
"message": "The signature does not include a secure timestamp."
}Solution:
Xcode automatically includes timestamps when using xcodebuild -exportArchive. If you see this error, you likely manually copied files from the archive. Use the proper export process.
Notarization History
View all your notarization submissions:
xcrun notarytool history \
--apple-id your-email@example.com \
--team-id YOUR_TEAM_ID \
--password xxxx-xxxx-xxxx-xxxxNotarizing Without Internet (Offline Verification)
Stapling the ticket allows Gatekeeper to verify your app offline:
- With stapled ticket: App runs immediately, no internet needed
- Without stapled ticket: Gatekeeper checks online (slower, requires internet)
Always staple before distribution.
Best Practices
- Automate credential storage: Use
notarytool store-credentialsto avoid typing credentials repeatedly - Always use
--wait: Easier to track progress than checking status manually - Keep logs: Save notarization logs for debugging future issues
- Test before distributing: Download from GitHub and test on a clean machine
- Staple immediately: Don't forget to staple after successful notarization
Security Notes
App-specific password:
- Use a unique password for notarization
- Don't share or commit passwords to git
- Rotate periodically for security
- This Skill prompts for it - never hardcode
Notarization is public:
- Notarization tickets are publicly verifiable
- EdDSA signatures in appcast.xml are public (safe to commit)
- Only the private key must be kept secret
Quick Reference
Submit:
xcrun notarytool submit FILE --apple-id EMAIL --team-id TEAM --password PASS --waitCheck status:
xcrun notarytool info SUBMISSION_ID --apple-id EMAIL --team-id TEAM --password PASSGet logs:
xcrun notarytool log SUBMISSION_ID --apple-id EMAIL --team-id TEAM --password PASSStaple:
xcrun stapler staple FILEVerify:
spctl -a -vvv APP.app