Skip to content

goncharik/PushTester

Repository files navigation

Apple Push Notification Sender

Python script to send push notifications to iOS devices using Apple's HTTP/2 APNs with .p8 certificate authentication.

Prerequisites

  1. Apple Developer Account with push notification capabilities
  2. .p8 Authentication Key from Apple Developer Console
  3. iOS app configured for push notifications
  4. Device token from your iOS app

Installation

pip install -r requirements.txt

Dependencies:

  • PyJWT - JWT token generation
  • cryptography - ES256 signing
  • httpx[http2] - HTTP/2 requests to APNs (required by Apple)
  • python-dotenv - Environment variable loading

🚀 Smart Environment Detection

The script now automatically detects the correct environment (Sandbox vs Production)! If you get a BadDeviceToken error, it will automatically retry with the opposite environment and tell you which one works.

Setup

1. Get Apple Developer Credentials

Team ID:

  • Apple Developer Console → Membership → Team ID (10 characters)

Key ID & .p8 File:

  • Apple Developer Console → Keys → Create Key
  • Enable "Apple Push Notifications service (APNs)"
  • Download the .p8 file (save it securely, can't re-download)
  • Note the Key ID (10 characters)

Bundle ID:

  • Your app's identifier (e.g., com.yourcompany.yourapp)

Device Token:

  • Get from your iOS app using:
// In your iOS app
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print("Device Token: \(token)")
}

2. Configure Environment Variables

Create a .env file by copying the example:

cp config.env.example .env

Edit .env with your actual values:

TEAM_ID=ABC123DEFG
KEY_ID=XYZ987WXYZ
KEY_FILE_PATH=AuthKey_XYZ987WXYZ.p8
BUNDLE_ID=com.yourcompany.yourapp
DEVICE_TOKEN=your64characterdevicetokenhere

Note: The .env file is automatically ignored by git for security. No need to specify sandbox vs production - the script auto-detects the correct environment!

Usage

Basic Usage

python push_notification.py

Programmatic Usage

from push_notification import APNSender

# Initialize
sender = APNSender(
    team_id="ABC123DEFG",
    key_id="XYZ987WXYZ", 
    key_file_path="AuthKey_XYZ987WXYZ.p8",
    bundle_id="com.yourcompany.yourapp",
    use_sandbox=True  # False for production
)

# Send notification
result = sender.send_notification(
    device_token="your_device_token_here",
    title="Hello World",
    body="This is a test notification",
    badge=5,
    sound="default",
    custom_data={
        "user_id": 123,
        "action": "open_chat"
    }
)

if result["success"]:
    print("✅ Sent successfully")
else:
    print(f"❌ Failed: {result['error']}")

Advanced Options

# Silent notification (no alert)
result = sender.send_notification(
    device_token="...",
    title="",
    body="",
    sound="",  # Empty string for silent
    custom_data={
        "content-available": 1,  # Background refresh
        "data": "your_data_here"
    }
)

# Custom sound
result = sender.send_notification(
    device_token="...",
    title="Custom Alert",
    body="With custom sound",
    sound="custom_sound.wav"  # Must be in app bundle
)

Environment Setup

Automatic Environment Detection

The script now automatically detects the correct environment! Just run it and it will:

  1. Start with sandbox environment (most common for development)
  2. If it gets BadDeviceToken, automatically try production environment
  3. Tell you which environment worked
  4. Remember the working environment for future calls
# The script will automatically figure out sandbox vs production
result = sender.send_notification(
    device_token="...",
    title="Test",
    body="Auto-detection!"
)

# Disable auto-retry if you want manual control
result = sender.send_notification(
    device_token="...",
    title="Test", 
    body="Manual mode",
    auto_retry=False
)

Manual Environment Control

# Force sandbox
sender = APNSender(..., use_sandbox=True)

# Force production  
sender = APNSender(..., use_sandbox=False)

Multiple Apps

# Override bundle ID for specific notification
result = sender.send_notification(
    device_token="...",
    title="Multi-app notification",
    body="Message",
    topic="com.yourcompany.anotherapp"  # Different app
)

Response Codes

Code Meaning
200 Success
400 Bad request (malformed JSON, missing fields)
403 Invalid certificate or topic
405 Method not allowed
410 Device token inactive (user uninstalled app)
413 Payload too large (max 4KB)
429 Too many requests
500 Internal server error

Troubleshooting

"Invalid JWT token":

  • Check team_id and key_id are correct
  • Verify .p8 file path and contents
  • Ensure key has APNs permission

"BadDeviceToken":

  • Device token must be 64 hex characters
  • Check sandbox vs production mismatch
  • Token may be expired/invalid

"TopicDisallowed":

  • Bundle ID doesn't match certificate
  • App not configured for push notifications

"PayloadTooLarge":

  • Max payload size is 4KB
  • Reduce title/body/custom_data size

File Structure

├── push_notification.py   # Main script
├── requirements.txt       # Dependencies
├── README.md             # This file
├── config.env.example    # Environment variables template
├── .env                  # Your actual environment variables (not tracked)
├── .gitignore           # Git ignore file
└── AuthKey_XXXXXXXXXX.p8 # Your .p8 certificate (not tracked)

Security Notes

  • Keep your .p8 file secure and private
  • Don't commit .p8 files to version control
  • Don't commit .env files to version control
  • JWT tokens expire after 1 hour (automatically refreshed)
  • Use environment variables for sensitive data in production
  • The .gitignore file automatically excludes .env and .p8 files from git

About

Simple python script to test iOS push notifications

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages