The finishAppUrl parameter
Configure a multi-step application process by using Indeed Apply.
- By using this API and its documentation and building an integration, you agree to the Additional API Terms and Guidelines.
What is finishAppUrl?
You can use the finishAppUrl parameter to configure a multi-step application process using Indeed Apply.
Including the finishAppUrl parameter in your IA configuration alerts job seekers of an additional step needed to complete the application process. Use this option to complete any processes required to apply for your jobs that your Indeed Apply integration does not support.
Parameters configured as part of the finishAppUrl are encrypted and passed to you when the job seeker initiates the additional step. This contrasts with POST data, which could take some time to receive.
Beginning August 1, 2026, finishAppURL will only be available to employers that meet the following criteria:
-
Employers with fewer than 1000 jobs on Indeed must spend greater than USD$300,000 annually.
-
Employers with 1000 jobs or more on Indeed must spend USD$500,000 annually.
Indeed will grant access to finishAppUrl automatically once an employer meets the above criteria.
Until August 1, 2026, new requests for access to finishAppUrl will be approved manually.
Criteria for finishAppUrl access
Use finishAppUrl to augment your Indeed Apply experience with your own:
- Third-party verification (e.g. background checks)
- Video interview (Have you considered using Indeed's Interview Platform?)
- Work sample submission
- Assessments (Have you considered using Indeed Assessments?)
- Unsupported screener question types (complex/relational, checkbox)
- Legal forms that require a mandated format (consult your legal team for specific requirements)
The finishAppUrl parameter should not be used for the following:
| Improper use | Description |
|---|---|
| Redundant Questions | The linked-to application must not contain questions already asked via Indeed Apply. |
| Account Creation | Account creation for the purpose of applying for a job must be completed through Indeed OAuth. |
If you intend to use finishAppUrl parameter in these ways, Indeed cannot approve your access.
Additional requirements
If you use finishAppUrl to send job seekers to additional steps for the application process, those steps must meet these requirements:
- Applications must be displayed as incomplete if the
finishAppUrlsection is not yet finished. - All partners must display the full count of applicants from Indeed. If an applicant does not finish the application on their end, they still have to display the count to their customers. They can display unfinished applications as “incomplete.”
- Job seeker application experience is optimized for mobile users.
- The domain used in the
finishAppUrlmust be added to Indeed’s allowlist to bypass the pop-up shown to job seekers before they are redirected. If the domain is not allowlisted, job seekers may see the pop-up before continuing to additional application steps.
To use the finishAppUrl parameter, contact marketplacesupport@indeed.com. Do not use finishAppUrl without prior approval from Indeed. If Indeed determines that you are using this parameter incorrectly, we can disable the integration until you resolve the issues.
Implement finishAppURL
Implement the finishAppUrl parameter as the following table and sections describe. To learn more about using other IA configuration parameters, see the IA Configuration parameters in the Direct Employer Integration document or the ATS Integration document.
| Parameter name | Required | Description | Example |
|---|---|---|---|
finishAppUrl | No | A URL that the job seeker is prompted to visit after applying by clicking Continue on the application confirmation page. Encode this URL in XML files. Do not use the |
%2Fapply%3FindeedID%3D%7Bindeed_apply_id%7D%26email%3D%7Bemail%7D%26phone%3D%7Bphone%7D |
Including the pingback
If you use the finishAppUrl parameter, Indeed requires that you send notification that the application has been completed. You do this by using a specific pingback URL.
Connect to the following URL:
https://apply.indeed.com/indeedapply/finish_application?indeed_apply_id=<YOUR_ID>&api_token=<YOUR_TOKEN>| Parameter | Description |
|---|---|
indeed_apply_id | The id field in the POST data for your application. |
api_token | The public API token provided by Indeed. |
Supported finishAppUrl parameters
You can add append parameters to the configured finishappURL and retrieve reusable information later. Supported parameters:
indeed_apply_id- Unique identifier for the applicationname- Job seeker’s full name (can only be used ifdata-indeed-apply-name="fullname"is configured explicitly or by default)email- Job seeker’s email addressphone- Job seeker’s phone number (if entered)firstname- Job seeker’s first name (can only be used ifdata-indeed-apply-name="firstlastname"is configured)lastname- Job seeker’s last name (can only be used ifdata-indeed-apply-name="firstlastname"is configured)
These parameters can be used in any order, and they can be assigned to any variable as part of finishAppUrl.
Example of finishAppUrl
data-indeed-apply-finishappurl="https://www.example.com/applyId={indeed_apply_id}&name={name}&email={email}&phone={phone}"Encryption and decryption
When you use finishAppUrl, Indeed encrypts and passes url parameters to the finishAppUrl shortly after job-seekers complete your Indeed Apply process.
The parameters are encrypted using the AES algorithm with your 128-bit secret key. The cipher mode is CBC with PKCS5 padding. The initialization vector is 16 bytes of 00.
To encrypt values
- Using your secret key, generate a 128-bit secret key using the first 16 bytes.
- Read the bytes of the plain-text email encoded in UTF-8.
- Encrypt the value using the AES algorithm and your 128-bit key. Be sure to use CBC mode and PKCS5 padding.
- Convert the encrypted bytes to a hex string.
- Use this hex string.
Encryption and decryption examples
Using C# (version 4.0)
using System.Text;using System.IO;using System;using System.Security.Cryptography;
public class IA_email_encryption_test {
public static void Main(string[] args) {
string email = "john.doe@example.com"; string key = "your api secret key";
// only use first 16 bytes of the key byte[] keybytes = Encoding.UTF8.GetBytes(key); byte[] truncatedkeybytes = new byte[16]; Array.Copy(keybytes, truncatedkeybytes, 16); // initialization vector is all 0's; no additonal initilization required byte[] iv = new byte[16];
byte[] ciphertext = Encrypt(email, truncatedkeybytes, iv);
string hexciphertext = ByteArrayToHexString(ciphertext);
if (!hexciphertext.Equals("eaaacff9df2e4c2a63083a303d4521f0bd41e375232a2895310179bc030addfb")) { Console.WriteLine("invalid encrypted value!"); } else { Console.WriteLine("hex encoded encrypted email: " + hexciphertext); // we decrypt merely as an exercise string decrypted = Decrypt(ciphertext, truncatedkeybytes, iv); Console.WriteLine("Decrypted E-mail: " + decrypted); } }
public static byte[] Encrypt(string plainText, byte[] key, byte[] iv) {
var cypher = new AesManaged(); cypher.Mode = CipherMode.CBC; cypher.Padding = PaddingMode.PKCS7; cypher.KeySize = 128; cypher.BlockSize = 128; cypher.Key = key; cypher.IV = iv;
var icTransformer = cypher.CreateEncryptor(); var msTemp = new MemoryStream();
var csEncrypt = new CryptoStream(msTemp, icTransformer, CryptoStreamMode.Write); var sw = new StreamWriter(csEncrypt); sw.Write(plainText); sw.Close(); sw.Dispose();
csEncrypt.Clear(); csEncrypt.Dispose();
byte[] bResult = msTemp.ToArray();
return bResult; }
public static string Decrypt(byte[] ciphertext, byte[] key, byte[] iv) {
var cypher = new AesManaged(); cypher.Mode = CipherMode.CBC; cypher.Padding = PaddingMode.PKCS7; cypher.KeySize = 128; cypher.BlockSize = 128; cypher.Key = key; cypher.IV = iv;
var icTransformer = cypher.CreateDecryptor(); var msTemp = new MemoryStream(ciphertext);
var csDecrypt = new CryptoStream(msTemp, icTransformer, CryptoStreamMode.Read); var sr = new StreamReader(csDecrypt);
string plaintext = sr.ReadToEnd();
csDecrypt.Clear(); csDecrypt.Dispose();
return plaintext; }
private static string ByteArrayToHexString(byte[] bytes) { StringBuilder sbHex = new StringBuilder(); foreach(byte b in bytes) sbHex.AppendFormat("{0:x2}", b); return sbHex.ToString(); }}Using Java (version 1.8)
import java.nio.ByteBuffer;import java.nio.charset.Charset;import java.lang.RuntimeException;import javax.crypto.Cipher;import javax.crypto.spec.*;import javax.crypto.spec.SecretKeySpec;
public class Main {
public static void main(String[] args) {
String email = "john.doe@example.com"; String apiSecret = "your api secret key"; String encrypted_email = encrypt(email, apiSecret); String decrypted_email = decrypt(encrypted_email, apiSecret); if (encrypted_email.equals("eaaacff9df2e4c2a63083a303d4521f0bd41e375232a2895310179bc030addfb")) { System.out.println("B64 encoded encrypted email: " + encrypted_email); System.out.println("decrypted email: " + decrypted_email); } else { System.out.println("invalid encrypted value!"); } }
static String encrypt(String message, String apiSecret) { try { // get api secret bytes byte[] keyBytes = apiSecret.getBytes(Charset.forName("UTF-8")); // get message bytes byte[] message_bytes = message.getBytes("UTF-8"); // note that we only use the first 16 bytes of the key SecretKeySpec key = new SecretKeySpec(keyBytes, 0, 16, "AES"); // get appropriate cipher using PKCS5 padding Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivspec = new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); cipher.init(Cipher.ENCRYPT_MODE, key, ivspec); // encrypt the message byte[] email_encrypted = cipher.doFinal(message_bytes); // this is the value that should be sent to Indeed return bytesToHexString(email_encrypted); } catch (Exception e) { System.out.println(e.getMessage()); throw new RuntimeException(e); } }
static String decrypt(String message, String apiSecret) { try { // get api secret bytes byte[] keyBytes = apiSecret.getBytes(Charset.forName("UTF-8")); // get message bytes byte[] message_bytes = message.getBytes(Charset.forName("UTF-8")); // convert from b64 encoding message_bytes = decodeHex(message_bytes); // Create a SecretKeySpec using api secret // note that we only use the first 16 bytes of the key SecretKeySpec key = new SecretKeySpec(keyBytes, 0, 16, "AES"); // get appropriate cipher using PKCS5 padding Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivspec = new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); cipher.init(Cipher.DECRYPT_MODE, key, ivspec); // decrypt the message byte[] email_decrypted = cipher.doFinal(message_bytes); return new String(email_decrypted, "UTF-8"); } catch (Exception e) { System.out.println(e.getMessage()); throw new RuntimeException(e); } }
static String bytesToHexString(byte[] in) { final StringBuilder builder = new StringBuilder(); for (byte b: in) { builder.append(String.format("%02x", b)); } return builder.toString(); }
static byte[] decodeHex(byte[] data) throws Exception { String text = new String(data, "UTF-8"); char[] chars = text.toCharArray();
int len = chars.length; byte[] out = new byte[len >> 1]; int i = 0;
for (int j = 0; j < len; ++i) { int f = toDigit(chars[j], j) << 4; ++j; f |= toDigit(chars[j], j); ++j; out[i] = (byte)(f & 255); } return out; }
static int toDigit(char ch, int index) throws Exception { int digit = Character.digit(ch, 16); if (digit == -1) { throw new Exception("Illegal hexadecimal character " + ch + " at index " + index); } else { return digit; } }}Using Perl (version 5.0)
use Crypt::CBC;use Encode;
# your secret keymy $secret = encode('UTF-8', 'your api secret key');# truncate key to 16 bytesmy $key = substr($secret, 0, 16);# initialization vector of zerosmy $iv = "\0"x 16;# create ciphermy $cipher = Crypt::CBC - & gt;new(-literal_key = & gt; 1, -header = & gt; 'none', -key = & gt; $key, -keysize = & gt; 16, -iv = & gt; $iv, -cipher = & gt; "Crypt::OpenSSL::AES");# Encrypt the UTF - 8 encoded string into a hex version of the datamy $email_encrypted = $cipher - & gt;encrypt_hex(encode('UTF-8', 'john.doe@example.com'));
if ($email_encrypted eq 'eaaacff9df2e4c2a63083a303d4521f0bd41e375232a2895310179bc030addfb') { print "Hex value of encrypted: $email_encryptedn"; # Decrypt the hex string to see if it 's still intact my $email_decrypted = $cipher - & gt; decrypt_hex($email_encrypted); print "Decrypted: $email_decryptedn";} else { print "invalild encrypted value! $email_encryptedn";}Using PHP (version 7.0)
function pkcs5_pad ($text) { $blocksize = 16; $pad = $blocksize - (strlen($text) % $blocksize); $text .= str_repeat(chr($pad), $pad); return $text;}
function pkcs5_unpad($text) { $pad = ord($text{strlen($text)-1}); if ($pad > strlen($text)) return false; if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; return substr($text, 0, -1 * $pad);}
function encrypt($str, $key) { $iv = str_repeat("\0", 16); $str = pkcs5_pad($str); $opts = OPENSSL_RAW_DATA; $encrypted = openssl_encrypt($str, 'AES-128-CBC', $key, $opts, $iv); return $encrypted;}
function decrypt($str, $key) { $iv = str_repeat(""\0", 16); $opts = OPENSSL_RAW_DATA; $decrypted = openssl_decrypt($str, 'AES-128-CBC', $key, $opts, $iv); return pkcs5_unpad($decrypted);}
$api_secret = "your api secret key";$email_address = "john.doe@example.com";
// truncate api-secret to first 16 bytes$newkey = mb_strcut($api_secret, 0, 16, "UTF8");// encrypt$encrypted = encrypt($email_address, $newkey);// we decrypt merely as an exercise$decrypted = decrypt($encrypted, $newkey);// this is the value to send to Indeed$encryptedhex = bin2hex($encrypted);if($encryptedhex != "eaaacff9df2e4c2a63083a303d4521f0bd41e375232a2895310179bc030addfba655e21c3309d9343206ae55866764e8") print("invalid encrypted hex value!");print("Hex value of encrypted: " . $encryptedhex . "n");print("Decrypted: " . $decrypted . "n");Using Python (version 2.7)
# https://pypi.python.org/pypi/pycryptodome/3.5.1from Crypto.Cipher import AES# https://pypi.python.org/pypi/pkcs7/0.1.2from pkcs7 import PKCS7Encoder# your secret keysecret = 'your api secret key'.encode('utf-8')# truncate key to 16 byteskey_bytes = secret[0:16]# initialization vector of zerosiv = '\0' * 16# the email address to encryptmessage_plaintext = 'john.doe@example.com'# pad the plaintext to 16 byte boundaryPKCS7encoder = PKCS7Encoder()message_plaintext_padded = PKCS7encoder.encode(message_plaintext);# encrypt the message bytescipher = AES.new(key_bytes, AES.MODE_CBC, iv)message_encrypted_raw = cipher.encrypt(message_plaintext_padded)# this is the value that should be sent to Indeedmessage_encrypted_hex = message_encrypted_raw.encode('hex')# we need a new instance for decrypt because the ciphers are statefuldecipher = AES.new(key_bytes, AES.MODE_CBC, iv)# we decrypt here simply as an exercisemessage_decrypted_raw = decipher.decrypt(message_encrypted_raw)# strip paddingmessage_decrypted = PKCS7encoder.decode(message_decrypted_raw)# confirm encrypted valueif message_encrypted_hex == "eaaacff9df2e4c2a63083a303d4521f0bd41e375232a2895310179bc030addfb": print 'Hex value of encrypted: ' + message_encrypted_hex print 'Decrypted: '+message_decryptedelse: print 'invalild encrypted value! ' + message_encrypted_hexUsing Python (version 3.6)
# https://pypi.python.org/pypi/pycryptodome/3.5.1from Crypto.Cipher import AES
# https://pypi.python.org/pypi/pkcs7/0.1.2from pkcs7 import PKCS7Encoder
# your secret keysecret = "your api secret key".encode("utf-8")# truncate key to 16 byteskey_bytes = secret[0:16]# initialization vector of zerosiv = bytes("\0" * 16, encoding="UTF-8")# the email address to encryptmessage_plaintext = "john.doe@example.com"# pad the plaintext to 16 byte boundaryPKCS7encoder = PKCS7Encoder()message_plaintext_padded = PKCS7encoder.encode(message_plaintext)# encrypt the message bytesmessage_bytes = message_plaintext_padded.encode("UTF-8")cipher = AES.new(key_bytes, AES.MODE_CBC, iv)message_encrypted_raw = cipher.encrypt(message_bytes)# this is the value that should be sent to Indeedmessage_encrypted_hex = message_encrypted_raw.hex()# we decrypt here simply as an exercisedecipher = AES.new(key_bytes, AES.MODE_CBC, iv)message_decrypted_raw = decipher.decrypt(message_encrypted_raw).decode("UTF-8")# strip paddingmessage_decrypted = PKCS7encoder.decode(message_decrypted_raw)# confirm encrypted valueif ( message_encrypted_hex == "eaaacff9df2e4c2a63083a303d4521f0bd41e375232a2895310179bc030addfb"): print("Hex value of encrypted: " + message_encrypted_hex) print("Decrypted: " + message_decrypted)else: print("invalild encrypted value! " + message_encrypted_hex)finishAppUrl with encrypted job seeker example
https://www.example.com/applyId=6339da3ba0f6f723851334e86a0f7c34cf00f51078cc770b0cace3277f6b99df&name=54fbec6bd96ea2dfbfe3133e7a0af84f&email=977c096217b6636e446066c75a449644&phone=e258e70b1c0e3e7de3d7c0d8e65f290bThe parameters in the example URL are encrypted and decrypted as follows:
| Parameter | Encrypted Value | Decrypted Value |
|---|---|---|
Name: | 54fbec6bd96ea2dfbfe3133e7a0af84f | Indeed Tester |
Email: | 977c096217b6636e446066c75a449644 | test@indeed.com |
Phone: | e258e70b1c0e3e7de3d7c0d8e65f290b | 1231231234 |