Skip to main content

The finishAppUrl parameter

Configure a multi-step application process with Indeed Apply.

legal notice

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.

FinishAppUrl parameter improper use
Improper useDescription
Redundant questionsThe linked application must not repeat questions that Indeed Apply already asks.
Account creationJob 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 finishAppUrl step.
  • 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 finishAppUrl domain 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.

Implement the finishAppUrl parameter
Parameter nameRequiredDescriptionExample
finishAppUrlNo

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 finishAppUrl parameter without approval from Indeed. See the following section.

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>
FinishAppUrl parameter
ParameterDescription
indeed_apply_idThe id field in the POST data for your application.
api_tokenThe 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 application
  • name — Job seeker's full name. Available only when data-indeed-apply-name="fullname" is set explicitly or by default.
  • email — Job seeker's email address
  • phone — Job seeker's phone number, if entered
  • firstname — Job seeker's first name. Available only when data-indeed-apply-name="firstlastname" is set.
  • lastname — Job seeker's last name. Available only when data-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}&amp;name={name}&amp;email={email}&amp;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:

  1. Generate a 128-bit secret key from the first 16 bytes of your secret key.
  2. Read the bytes of the plain-text email in UTF-8.
  3. Encrypt the value with the AES algorithm and your 128-bit key, using CBC mode and PKCS5 padding.
  4. 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 key
my $secret = encode('UTF-8', 'your api secret key');
# truncate key to 16 bytes
my $key = substr($secret, 0, 16);
# initialization vector of zeros
my $iv = "\0"
x 16;
# create cipher
my $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 data
my $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.1
from Crypto.Cipher import AES
# https://pypi.python.org/pypi/pkcs7/0.1.2
from pkcs7 import PKCS7Encoder
# your secret key
secret = 'your api secret key'.encode('utf-8')
# truncate key to 16 bytes
key_bytes = secret[0:16]
# initialization vector of zeros
iv = '\0' * 16
# the email address to encrypt
message_plaintext = 'john.doe@example.com'
# pad the plaintext to 16 byte boundary
PKCS7encoder = PKCS7Encoder()
message_plaintext_padded = PKCS7encoder.encode(message_plaintext);
# encrypt the message bytes
cipher = 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 Indeed
message_encrypted_hex = message_encrypted_raw.encode('hex')
# we need a new instance for decrypt because the ciphers are stateful
decipher = AES.new(key_bytes, AES.MODE_CBC, iv)
# we decrypt here simply as an exercise
message_decrypted_raw = decipher.decrypt(message_encrypted_raw)
# strip padding
message_decrypted = PKCS7encoder.decode(message_decrypted_raw)
# confirm encrypted value
if message_encrypted_hex == "eaaacff9df2e4c2a63083a303d4521f0bd41e375232a2895310179bc030addfb":
print 'Hex value of encrypted: ' + message_encrypted_hex
print 'Decrypted: '+message_decrypted
else:
print 'invalild encrypted value! ' + message_encrypted_hex

Using Python (version 3.6)​

# https://pypi.python.org/pypi/pycryptodome/3.5.1
from Crypto.Cipher import AES
# https://pypi.python.org/pypi/pkcs7/0.1.2
from pkcs7 import PKCS7Encoder
# your secret key
secret = "your api secret key".encode("utf-8")
# truncate key to 16 bytes
key_bytes = secret[0:16]
# initialization vector of zeros
iv = bytes("\0" * 16, encoding="UTF-8")
# the email address to encrypt
message_plaintext = "john.doe@example.com"
# pad the plaintext to 16 byte boundary
PKCS7encoder = PKCS7Encoder()
message_plaintext_padded = PKCS7encoder.encode(message_plaintext)
# encrypt the message bytes
message_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 Indeed
message_encrypted_hex = message_encrypted_raw.hex()
# we decrypt here simply as an exercise
decipher = AES.new(key_bytes, AES.MODE_CBC, iv)
message_decrypted_raw = decipher.decrypt(message_encrypted_raw).decode("UTF-8")
# strip padding
message_decrypted = PKCS7encoder.decode(message_decrypted_raw)
# confirm encrypted value
if (
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&amp;name=54fbec6bd96ea2dfbfe3133e7a0af84f&amp;email=977c096217b6636e446066c75a449644&amp;phone=e258e70b1c0e3e7de3d7c0d8e65f290b

Indeed encrypts and decrypts the parameters in the example URL as follows:

Encrypted and decrypted
ParameterEncrypted valueDecrypted value
name54fbec6bd96ea2dfbfe3133e7a0af84fIndeed Tester
email977c096217b6636e446066c75a449644test@indeed.com
phonee258e70b1c0e3e7de3d7c0d8e65f290b1231231234

On this page

  • What is finishAppUrl?
  • Criteria for finishAppUrl access
    • Additional requirements
    • Implement finishAppUrl
    • Including the pingback
    • Encryption and decryption