The finishAppUrl parameter
Configure a multi-step application process with 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?
The finishAppUrl parameter adds a step to your Indeed Apply application process. When you include it, Indeed tells job seekers that one more step remains before their application is complete.
Use finishAppUrl for steps that your Indeed Apply integration does not support on its own.
Indeed encrypts the parameters you configure in finishAppUrl and passes them to you as soon as the job seeker starts the extra step. POST data can take longer to arrive.
Use finishAppUrl to add your own:
- Third-party verification, such as a background check
- Video interview — consider Indeed's Interview Platform instead
- Work sample submission
- Assessment — consider Indeed Assessments instead
- Unsupported screener question types, such as complex, relational, and checkbox questions
- Legal forms that require a mandated format — consult your legal team for the requirements
Do not use finishAppUrl for the following. Indeed does not approve access for these uses.
| Improper use | Description |
|---|---|
| Redundant questions | The linked application must not repeat questions that Indeed Apply already asks. |
| Account creation | Job seekers must create an account through Indeed OAuth. |
Criteria for finishAppUrl access
New users must meet these spend requirements. Existing users keep their current finishAppUrl setup, and these requirements do not apply to them.
- In the US, CA, BE, CH, DE, ES, FR, GB, IE, and NL:
- Employers with fewer than 1,000 jobs on Indeed must spend more than USD $300,000 annually.
- Employers with 1,000 jobs or more on Indeed must spend USD $500,000 annually.
- In all other countries:
- Employers with fewer than 1,000 jobs on Indeed must spend more than USD $50,000 annually.
- Employers with 1,000 jobs or more on Indeed must spend USD $100,000 annually.
Only searchable and sponsored jobs count toward these thresholds.
Indeed grants access to finishAppUrl automatically when an employer meets these criteria.
Additional requirements
The extra steps you send job seekers to must meet these requirements:
- Show an application as incomplete until the job seeker finishes the
finishAppUrlstep. - Show your customers the full count of applicants from Indeed, including job seekers who do not finish the extra step. Label those applications as incomplete.
- Optimize the application experience for mobile.
- Add your
finishAppUrldomain to Indeed's allowlist. Otherwise, job seekers see a pop-up before Indeed redirects them.
To use finishAppUrl, contact marketplacesupport@indeed.com. Do not use it without approval from Indeed. If Indeed finds that you use the parameter incorrectly, it can disable your integration until you fix the issues.
Implement finishAppUrl
Implement finishAppUrl as the following table and sections describe. For the other Indeed Apply configuration parameters, see the Indeed Apply integration guide for direct employers or Indeed Apply configuration parameter restrictions.
| Parameter name | Required | Description | Example |
|---|---|---|---|
finishAppUrl | No | A URL that job seekers visit after they apply, when they select Continue on the application confirmation page. Encode this URL in XML files. Do not use the | http%3A%2F%2Fwww.apply.com%2Fapply%3FindeedID%3D%7Bindeed_apply_id%7D%26email%3D%7Bemail%7D%26phone%3D%7Bphone%7D |
Including the pingback
If you use finishAppUrl, you must tell Indeed when the job seeker completes the application. Send that notification to this pingback 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 that Indeed provides. |
Supported finishAppUrl parameters
Append parameters to your finishAppUrl to receive information that you can reuse later. finishAppUrl supports these parameters:
indeed_apply_id— Unique identifier for the applicationname— Job seeker's full name. Available only whendata-indeed-apply-name="fullname"is set explicitly or by default.email— Job seeker's email addressphone— Job seeker's phone number, if enteredfirstname— Job seeker's first name. Available only whendata-indeed-apply-name="firstlastname"is set.lastname— Job seeker's last name. Available only whendata-indeed-apply-name="firstlastname"is set.
Use these parameters in any order, and assign them to any variable in 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 the url parameters and passes them to your finishAppUrl shortly after job seekers complete your Indeed Apply process.
Indeed encrypts the parameters with the AES algorithm and your 128-bit secret key. The cipher mode is CBC with PKCS5 padding, and the initialization vector is 16 bytes of 00.
To encrypt a value:
- Generate a 128-bit secret key from the first 16 bytes of your secret key.
- Read the bytes of the plain-text email in UTF-8.
- Encrypt the value with the AES algorithm and your 128-bit key, using CBC mode and PKCS5 padding.
- Convert the encrypted bytes to a 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=e258e70b1c0e3e7de3d7c0d8e65f290bIndeed encrypts and decrypts the parameters in the example URL as follows:
| Parameter | Encrypted value | Decrypted value |
|---|---|---|
name | 54fbec6bd96ea2dfbfe3133e7a0af84f | Indeed Tester |
email | 977c096217b6636e446066c75a449644 | test@indeed.com |
phone | e258e70b1c0e3e7de3d7c0d8e65f290b | 1231231234 |