Maintain Unique Device ID for Mobile Devices

You can maintain a unique Punchh device ID for mobile devices (iOS and Android) to ensure that once-per-device validation is respected for features and functionalities in the Punchh platform. A simple example would be a single sign-up offer in case of multiple sign-ups from the same device.

iOS

iOS devices do not have access to the IMEI number of the mobile phone. Instead, they generate a UUID whenever an app is installed. Additionally, the iTunes Apple ID (which is the ID of the app) is appended to the UUID. This is useful for uniquely identifying an app for each business.

Whenever an app is installed on an iOS device, the app looks for this unique ID in the keychain. If this unique ID is not found, the app creates one and writes in the keychain. This is then sent to the Punchh server as the Punchh device ID. Reinstalling the same app will not affect the ID already stored in the keychain.

You need to pass this unique ID in the punchh-app-device-id header to help Punchh uniquely identify each device so that rewards can be awarded individually to each device instead of per user.

This ID is deleted after a factory reset and therefore affects server-side features, such as gifting only a single sign-up offer per device in case multiple sign-ups happen from the same device.

Objective C

/// itunsAppID=1234567890, then=> APP_DEVICE_UUID_GOES_HERE-1234567890

+ (NSString *)getDeviceIdentifier {
    NSString *iTunesAppId = [NSBundle iTunesAppleID];
    NSString *identifier = [UICKeyChainStore stringForKey:iTunesAppId];
    if (![identifier present]) {
        identifier = [NSString stringWithFormat:@"%@-%@", [[NSUUID UUID] UUIDString], iTunesAppId];
        [UICKeyChainStore setString:identifier forKey:iTunesAppId];
    }
    return identifier;
}

Swift

/// Unique device identifier for single offer concepts
    public static let deviceIdentifier: String = {
        let itunesAppID = Bundle.iTunesAppID
        guard let identifier = string(key: itunesAppID) else {
            let recretedIdentifier = "\(NSUUID().uuidString)-\(itunesAppID)"
            if set(string: recretedIdentifier, key: recretedIdentifier) {
                return recretedIdentifier
            } else {
                return "unknown"
            }
        }
        return identifier
    }()

Android

Method 1: Using the IMEI Number

Android applications can request the TelephonyManager permission from the user to fetch the IMEI number of the device. This IMEI number is used as a parameter in the function to generate a UUID. The final value returned from this function is called the Punchh device ID. You need to pass this Punchh device ID in the punchh-app-device-id header to help Punchh uniquely identify each device so that rewards can be awarded individually to each device instead of per user.

This ID is retained after a factory reset. Since the IMEI number is used, every repeated installation will generate the same Punchh device ID.

The drawback, however, is that users may not approve of a loyalty app that requires TelephonyManager permission, which includes having permission to make calls, send messages, etc.

Method 2: Using the Android ID

Android applications can use the Android ID (also known as the SSAID) to generate the Punchh device ID. To use the Android ID, no explicit permission is required from users. You need to pass this Android ID in the punchh-app-device-id header to help Punchh uniquely identify each device so that rewards can be awarded individually to each device instead of per user.

This ID is deleted after a factory reset and therefore affects server-side features, such as gifting only a single sign-up offer per device in case multiple sign-ups happen from the same device.

Java

package com.punchh.services;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.support.v4.content.ContextCompat;
import android.telephony.TelephonyManager;
import android.text.TextUtils;

import com.punchh.application.PunchhApplication;
import com.punchh.constants.Constants;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Random;
import java.util.UUID;

public class DeviceId {
    public static final String FIXED_ANDROID_ID = "ID_GOES_HERE";
    private static final String INSTALLATION = "CONSTANT_INSTALLATION_APP_GENERATED_ID";
    // private static String sID = null;


public synchronized static String getId(Context context) {
    String deviceId = DeviceResourceHandler.getInstance(context).getDataFromSharedPref(Constants.DEVICE_ID);
    if (TextUtils.isEmpty(deviceId)) {
        File installation = new File(context.getFilesDir(), INSTALLATION);
        try {
            if (!installation.exists()) {

                UUID deviceUuid = new UUID(getDeviceUniqueId(context).hashCode(), context.getApplicationContext()
                        .getPackageName().hashCode());
                deviceId = deviceUuid.toString();
                writeInstallationFile(installation, deviceId);
                DeviceResourceHandler.getInstance(context).addToSharedPref(Constants.DEVICE_ID, deviceId);
                return deviceId;
            }
            deviceId = readInstallationFile(installation);
            DeviceResourceHandler.getInstance(context).addToSharedPref(Constants.DEVICE_ID, deviceId);
        } catch (Exception e) {
            if (!PunchhApplication.getAppliation().isRelease()) {
                e.printStackTrace();
            }
        }
    }
    return deviceId;
}

private static String readInstallationFile(File installation) throws IOException {
    RandomAccessFile f = new RandomAccessFile(installation, "r");
    byte[] bytes = new byte[(int) f.length()];
    f.readFully(bytes);
    f.close();
    return new String(bytes);
}

private static void writeInstallationFile(File installation, String id) throws IOException {
    FileOutputStream out = new FileOutputStream(installation);
    out.write(id.getBytes());
    out.close();
}

private static String getDeviceUniqueId(Context context) {
    String deviceIMEI = null;
    TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
        deviceIMEI = mTelephonyManager.getDeviceId();
    }

    if (deviceIMEI == null || deviceIMEI.equals("")) {
        return getAndroidId(context);
    } else {
        return deviceIMEI;
    }
}

private static String getAndroidId(Context context) {
    String androidId = Settings.Secure.getString(context.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);

    if (androidId.equalsIgnoreCase(FIXED_ANDROID_ID)) {
        return getSerialBuildId();
    } else
        return androidId;
}

private static String getSerialBuildId() {
    if (!TextUtils.isEmpty(android.os.Build.SERIAL)) {
        return android.os.Build.SERIAL;
    } else
        return getRandomId();
}

private static String getRandomId() {
    //If nothing is found for this device, generate a random number, although this case is not possible.
    Random tmpRand = new Random();
    return String.valueOf(tmpRand.nextLong());
}
}
Copyright © 2025 PAR Technology Corporation. All rights reserved.
PAR Technology Corporation 8383 Seneca Turnpike, Suite 3 New Hartford, New York 13413 (315) 738-0600 legal@partech.com. PAR Tech is a leading global provider of software, systems, and service solutions to the restaurant and retail industries.
You may learn about its product offerings here.
Before using this application, please read the Limited License Agreement and the PAR Tech Terms of Use.