Skip to main content

Embedded Sponsorships Integration Guide

About ZipRecruiter Embedded Sponsorships​

By utilizing a lightweight, secure flow (e.g. "Widget"), employers can sponsor jobs and process payments via credit card without ever leaving your platform.

Key Benefits:

  • Seamless UX: Employers stay within their workflow in your ATS, reducing friction and increasing conversion.
  • Turnkey Account Creation and Billing: Streamlined onboarding with integrated Terms of Use acceptance. ZipRecruiter handles the billing with the employer.
  • Automated Sync: Candidates from ZipRecruiter are delivered directly back to the ATS.
  • Rapid Deployment: Minimal engineering overhead.
  • Revenue share: ATS partners receive a revenue-share for sponsored jobs.

Technical Prerequisites: To leverage the Embedded Sponsorship integration, please implement:

Complete the Integration in 6 Steps​

Get a sponsorship button working in your ATS in six steps.

1. Get credentials from your ZipRecruiter partner contact:

  • Jobs API Key, for posting jobs.
  • Sponsorship API Key, for job sponsorship. Delivered as a single Authorization: Basic credential that encodes a client_id / client_secret pair.
  • Domain Whitelisting, provide all domain(s) where you will embed the SDK, including development, testing, and production environments (e.g., https://app.yourplatform.com, https://staging.yourplatform.com). ZipRecruiter uses these to configure the frame-ancestors Content Security Policy directive. The iframe will not load until your domains are whitelisted.

2. Post a job via the Jobs API. A job must exist before it can be sponsored:

curl -X POST https://api.ziprecruiter.com/partner/v0/job \
-H "Authorization: Basic YOUR_JOB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"job_id": "job-001",
"employer_id": "emp-456",
"employer_name": "Acme Corp",
"ats_user_id": "user-789",
"title": "Senior Software Engineer",
"job_type": "full_time",
"city": "San Francisco",
"state": "CA",
"postal_code": "94105",
"country": "US",
"description": "<div>Join our engineering team as a Senior Software Engineer.</div>"
}'

3. Map user roles: Map your ATS user types to the roles passed in the token request. See User Roles Mapping for the role definitions and example mappings.

4. Exchange credentials for an access token. This scopes a short-lived token to a specific employer and user so the widget knows whose sponsorship flow to show. This call must happen on your server:

curl -X POST https://api.ziprecruiter.com/ats-embed/v0/api/token \
-H "Authorization: Basic YOUR_SPONSORSHIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "hiring@acme.com",
"externalEmployerId": "emp-456",
"atsUserId": "user-789",
"roles": ["can-sponsor", "can-view-all-jobs", "can-manage-billing"]
}'
note

ZipRecruiter delivers this credential pre-encoded, so paste it directly after Basic . It decodes to client_id:client_secret; if your HTTP client needs them split (e.g. curl's -u client_id:client_secret), base64-decode the credential first.

5. Include the SDK on your ATS page and annotate each job element:

Part 1: Include the script tag

<script
src="https://api.ziprecruiter.com/ats-embed/v1/sdk.js"
data-partner-token="ACCESS_TOKEN_FROM_STEP_4"
data-base-url="https://api.ziprecruiter.com"
async
></script>
note

v1 is the current SDK version. If you already integrated against /ats-embed/v0/sdk.js, see SDK Versions for how to upgrade and Version History for what changed.

Part 2: Add data-zr-job-id to each job element

<div class="job-listing" data-zr-job-id="job-001">
<h3>Senior Software Engineer</h3>
<p>San Francisco, CA</p>
</div>

6. The SDK automatically renders a "Sponsor on ZipRecruiter" button inside each annotated element. When a user clicks it, the sponsorship modal opens. On completion, the button updates to "Pause ZipRecruiter Sponsorship."

Server-side Token Exchange Guide​

Your server calls POST /ats-embed/v0/api/token with Basic authentication using your Sponsorship API credentials. The response contains a short-lived access token (JWT). All subsequent API calls and SDK initialization use this token as Authorization: Bearer <access_token>.

Partner Server                              ZipRecruiter API
| |
| POST /ats-embed/v0/api/token |
| Authorization: Basic <sponsorship_api_key> |
| { email, externalEmployerId, ... } |
| -------------------------------------------> |
| |
| { access_token, expires_in, user } |
| <------------------------------------------- |
| |
| POST /ats-embed/v0/api/sponsored_status |
| Authorization: Bearer <access_token> |
| -------------------------------------------> |
| |

See Token Exchange in the Sponsorship API reference for the full request/response schema.

Token Lifecycle​

The Sponsorship API does not use a refresh_token flow. To get a new token, call POST /ats-embed/v0/api/token again with the same credentials.

PropertyValue
Lifetime2 hours (expires_in: 7200)
Refresh mechanismRe-call /ats-embed/v0/api/token
StorageServer-side only; never expose your Sponsorship API credential to the browser

Recommended caching pattern:

token_cache = {}

# sponsorship_api_credential is the Authorization: Basic value ZipRecruiter provides (see Step 4).
def get_token(sponsorship_api_credential, email, employer_id, ats_user_id, roles):
cache_key = f"{employer_id}:{ats_user_id}"
cached = token_cache.get(cache_key)

if cached and cached["expires_at"] > time.time() + 300:
return cached["access_token"]

resp = requests.post(
"https://api.ziprecruiter.com/ats-embed/v0/api/token",
headers={"Authorization": f"Basic {sponsorship_api_credential}"},
json={
"email": email,
"externalEmployerId": employer_id,
"atsUserId": ats_user_id,
"roles": roles,
},
)
data = resp.json()
token_cache[cache_key] = {
"access_token": data["access_token"],
"expires_at": time.time() + data["expires_in"],
}
return data["access_token"]

Key points:

  • Refresh before the token expires (the example above uses a 5-minute buffer).
  • Do not request a fresh token for every API call, as this will trigger rate limits.
  • Pass the token to the frontend only as needed (e.g., data-partner-token on the SDK script tag).

User Roles Mapping​

The token request includes a roles array that controls what the user can do in the sponsorship widget. Map your ATS user types to the appropriate roles.

RolePurpose
can-sponsorRequired to sponsor jobs. Without this role, the user sees a "no access" screen.
can-view-all-jobsWith this role, the user sees all company jobs on the job selection screen. Without it, they see only jobs they own (and atsUserId is required).
can-unsponsorRequired to stop sponsorship on a job.
can-manage-billingRequired to add or change the payment method. Without it, the user can still sponsor using an existing card on file but cannot replace it.

Example mappings:

ATS user typeRoles
Admin / hiring managercan-sponsor, can-view-all-jobs, can-unsponsor, can-manage-billing
Recruitercan-sponsor, can-view-all-jobs, can-unsponsor

Best Practices​

  • Cache access tokens. Tokens are valid for 2 hours. Request a new token only when the current one is about to expire.
  • Respect Retry-After. When you receive a 429, wait the indicated number of seconds before retrying.
  • Use exponential backoff. If retries continue to receive 429 responses, increase the delay between attempts.

End-User Experience​

When a user opens the sponsorship widget, the screens they see depend on their account state. The email and externalEmployerId provided in the token request determine which flow applies.

Returning User (returning)​

User and org both exist. No additional verification needed.

StepScreenDescription
1Select JobsChoose which jobs to sponsor
2Distribution LevelSelect distribution level and enter payment info
3ProcessingPayment processing
4SuccessConfirmation; triggers zr:job_sponsored

New User (new)​

Neither user nor org found. Registration form is shown (pre-filled from token request fields).

StepScreenDescription
1ConnectEnter email or sign in with Google
2VerifyEnter 6-digit OTP sent to email
3Confirm DetailsReview/edit company name, website, location, contact info
4Select JobsChoose which jobs to sponsor
5Distribution LevelSelect distribution level and enter payment info
6ProcessingPayment processing
7SuccessConfirmation; triggers zr:job_sponsored

Identity Verification (verify-identity)​

User has not yet verified their identity with this partner. Once verified, the user is not re-prompted on future visits.

StepScreenDescription
1ConnectEnter email or sign in with Google
2VerifyEnter 6-digit OTP sent to email
3Select JobsChoose which jobs to sponsor
4Distribution LevelSelect distribution level and payment
5ProcessingPayment processing
6SuccessConfirmation; triggers zr:job_sponsored

New User for Existing Org​

The externalEmployerId matches an existing employer but no account exists for the email. User is auto-created and associated with the existing org.

StepScreenDescription
1ConnectEnter email or sign in with Google
2VerifyEnter 6-digit OTP sent to email
3Select JobsChoose which jobs to sponsor
4Distribution LevelSelect distribution level and enter payment info
5ProcessingPayment processing
6SuccessConfirmation; triggers zr:job_sponsored

No Access (no-access)​

User account is inactive.

StepScreenDescription
1No AccessInformational screen; no further steps available

Manual Integration (Only if not using the SDK)​

For more control to manage communication between your site and the modal, you can skip the SDK. However, we recommend the SDK over the manual integration.

Checking Sponsorship Status​

Call POST /ats-embed/v0/api/sponsored_status with the external job IDs visible on the page to determine which jobs are currently sponsored:

curl -X POST "https://api.ziprecruiter.com/ats-embed/v0/api/sponsored_status" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{ "jobIds": ["job-010", "job-011"] }'

See Sponsored Status in the API Reference for the full schema.

Opening Embed Flows​

Open the appropriate URL in an iframe. The widget handles job selection, payment, and all interactions internally.

Sponsor flow:

https://api.ziprecruiter.com/ats-embed/v0/?jobId=<external_job_id>&token=<access_token>

Manage sponsorship flow:

https://api.ziprecruiter.com/ats-embed/v0/manage?jobId=<external_job_id>&token=<access_token>
ParameterDescription
jobIdExternal job ID
tokenAccess token from the token exchange

postMessage Protocol​

The widget iframe communicates with your page via window.postMessage. Always verify event.origin before processing messages. Only trust messages from https://api.ziprecruiter.com.

zr:job_sponsored: Sent when the user completes the sponsorship flow.

{
"type": "zr:job_sponsored",
"jobIds": ["job-001", "job-003"]
}

Recommended action: Close the modal, refresh your job list, update sponsored status.

zr:unsponsor: Sent when the user stops sponsoring a job.

{
"type": "zr:unsponsor",
"jobId": "job-001"
}

Recommended action: Close the modal and update the job's button state back to "Sponsor on ZipRecruiter."

zr:token_update: Sent after new user registration completes. Contains a refreshed access token that supersedes the one passed at SDK initialization.

{
"type": "zr:token_update",
"token": "<new_access_token>"
}

Recommended action: Store the new token server-side and use it for any subsequent calls to the Sponsorship API. If you pass the token to the SDK via data-partner-token, update that value as well.

zr:modal_close: Sent when the user closes the widget without completing the flow.

{
"type": "zr:modal_close"
}

Recommended action: Close the modal. No job state has changed.

zr:resize_iframe: Sent when the widget content height changes.

{
"type": "zr:resize_iframe",
"height": 580
}

Recommended action: Update the iframe's height to the specified value.

Keeping UI in Sync​

When you receive zr:job_sponsored (or zr:modal_close after a manage flow):

  1. Close the sponsorship modal/iframe.
  2. Call POST /ats-embed/v0/api/sponsored_status to refresh statuses.
  3. Update buttons and badges in your UI.

Security Appendix​

Authentication​

During onboarding you receive two independent credentials:

CredentialWhat You ReceiveUsed ForHow It's Sent
Jobs API KeyA single API key stringPosting, updating, and deleting jobs via the Jobs APIAuthorization: Basic <job_api_key>
Sponsorship API KeyA single Authorization: Basic credential (encodes a client_id / client_secret pair)Obtaining an access token for the Sponsorship APIAuthorization: Basic <sponsorship_api_key>
note

Your Jobs API Key does not work with the Sponsorship API, and your Sponsorship API Key does not work with the Jobs API. They are completely separate credentials.

  • Keep your Sponsorship API credential confidential. Never embed it in frontend code, mobile apps, or client-side JavaScript.
  • If you believe your credentials have been compromised, contact ZipRecruiter immediately for rotation.

Token Handling​

  • Access tokens are short-lived (2 hours). Cache them server-side and refresh before expiry.
  • Never expose your Sponsorship API credential to frontend JavaScript. The token exchange must happen on your server.
  • Pass access tokens to the frontend only as needed (e.g., as the data-partner-token SDK attribute).

Iframe Security / CSP / CORS​

  • The embed iframe does not rely on third-party cookies. Authentication is token-based (JWT in sessionStorage, scoped to the iframe origin).
  • ZipRecruiter sets Content-Security-Policy: frame-ancestors to restrict which domains can embed the widget. Your domain is whitelisted during onboarding.
  • The SDK script is served with Access-Control-Allow-Origin: * to allow cross-origin loading.
  • No X-Frame-Options header is set on embed endpoints, allowing iframe embedding on approved partner domains.

What you need to do:

  1. Provide all domain(s) to your ZipRecruiter partner contact, including development, testing, and production environments, so we can add them to the frame-ancestors CSP directive. The widget iframe will not load until your domains are whitelisted.
  2. Serve your pages over HTTPS (required for iframe embedding).
  3. If your site sets its own CSP, add frame-src https://api.ziprecruiter.com to allow the ZipRecruiter iframe.

HTTPS​

All API endpoints and embed URLs use HTTPS. Non-HTTPS requests are rejected.

Testing in Production​

All integration development and testing uses the production environment at https://api.ziprecruiter.com. There is no separate sandbox.

Testing guidelines:

  • Use test job postings during development and integration testing.
  • Real payments will be processed when sponsoring jobs. ZipRecruiter will refund any sponsorship charges incurred during testing. Coordinate with your partner contact before you begin testing.
  • Ensure your domain has been whitelisted (see Step 1 above) before attempting to load the widget.

Glossary​

TermDefinition
external_job_idThe unique job identifier in your (partner) system. Used to correlate jobs between your ATS and ZipRecruiter.
emailThe user's email address. Passed during token exchange to identify or create the employer user.
externalEmployerIdThe unique employer identifier in your system. Must be consistent across environments. Used to link a partner employer to a ZipRecruiter organization.
Access TokenA short-lived Bearer token (JWT) returned by the token exchange endpoint. Valid for 2 hours.
Jobs API KeyA single API key string issued during onboarding. Authenticates requests to the Jobs API via Authorization: Basic header.
Sponsorship API KeyA single Authorization: Basic credential issued during onboarding (encodes a client_id:client_secret pair). Used to obtain an access token from the token exchange endpoint.
Jobs APIThe ZipRecruiter API for posting, updating, and removing job listings (organic/unpaid). Authenticated with the Jobs API Key.
Sponsorship APIThe API for promoting jobs with paid visibility. Authenticated via token exchange and Bearer tokens.
SDKThe ZipRecruiter JavaScript SDK (sdk.js) that manages widget rendering, iframe lifecycle, and event communication.
postMessageThe browser window.postMessage API used for cross-origin communication between the widget iframe and your ATS page.
CSPContent Security Policy. An HTTP header that controls which domains can embed or be embedded by a page.
CORSCross-Origin Resource Sharing. HTTP headers that control which origins can make API requests to a server.
OTPOne-Time Passcode. A 6-digit code sent via email for identity verification when linking an existing account.

Guide Changelog​

VersionDateDescription
V0.0.32026-06-15Corrected the postMessage protocol to match the SDK (zr:resize renamed to zr:resize_iframe, removed the non-existent zr:upgrade_requested, documented zr:unsponsor and zr:token_update). Fixed the verify-identity description and added the OTP verification steps to the new-user flows. Clarified that the Sponsorship API credential is delivered as a single Authorization: Basic header (encoding a client_id:client_secret pair) and corrected the token-exchange curl example accordingly. Added this Glossary.
V0.0.22026-04-01Replaced sandbox/dev URLs with production URLs (https://api.ziprecruiter.com). Added explicit domain whitelisting requirements. Updated testing guidance for production environment.
V0.0.12026-03-03Updated the Jobs API curl example in Quick Start to include all required fields (employer_name, ats_user_id, city, state, description) and corrected postal_code to match the San Francisco location. Partners following the previous example received 400 validation errors.