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:
- Jobs API: Sync jobs data for sponsorships
- Questions API: Support screening questions
- Apply Webhook: Native apply integration & candidate delivery
- Hiring Signals: Report hiring-related events regarding applications
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: Basiccredential that encodes aclient_id/client_secretpair. - 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 theframe-ancestorsContent 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"]
}'
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>
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.
| Property | Value |
|---|---|
| Lifetime | 2 hours (expires_in: 7200) |
| Refresh mechanism | Re-call /ats-embed/v0/api/token |
| Storage | Server-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-tokenon 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.
| Role | Purpose |
|---|---|
can-sponsor | Required to sponsor jobs. Without this role, the user sees a "no access" screen. |
can-view-all-jobs | With 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-unsponsor | Required to stop sponsorship on a job. |
can-manage-billing | Required 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 type | Roles |
|---|---|
| Admin / hiring manager | can-sponsor, can-view-all-jobs, can-unsponsor, can-manage-billing |
| Recruiter | can-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 a429, wait the indicated number of seconds before retrying. - Use exponential backoff. If retries continue to receive
429responses, 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.
| Step | Screen | Description |
|---|---|---|
| 1 | Select Jobs | Choose which jobs to sponsor |
| 2 | Distribution Level | Select distribution level and enter payment info |
| 3 | Processing | Payment processing |
| 4 | Success | Confirmation; triggers zr:job_sponsored |
New User (new)
Neither user nor org found. Registration form is shown (pre-filled from token request fields).
| Step | Screen | Description |
|---|---|---|
| 1 | Connect | Enter email or sign in with Google |
| 2 | Verify | Enter 6-digit OTP sent to email |
| 3 | Confirm Details | Review/edit company name, website, location, contact info |
| 4 | Select Jobs | Choose which jobs to sponsor |
| 5 | Distribution Level | Select distribution level and enter payment info |
| 6 | Processing | Payment processing |
| 7 | Success | Confirmation; 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.
| Step | Screen | Description |
|---|---|---|
| 1 | Connect | Enter email or sign in with Google |
| 2 | Verify | Enter 6-digit OTP sent to email |
| 3 | Select Jobs | Choose which jobs to sponsor |
| 4 | Distribution Level | Select distribution level and payment |
| 5 | Processing | Payment processing |
| 6 | Success | Confirmation; 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.
| Step | Screen | Description |
|---|---|---|
| 1 | Connect | Enter email or sign in with Google |
| 2 | Verify | Enter 6-digit OTP sent to email |
| 3 | Select Jobs | Choose which jobs to sponsor |
| 4 | Distribution Level | Select distribution level and enter payment info |
| 5 | Processing | Payment processing |
| 6 | Success | Confirmation; triggers zr:job_sponsored |
No Access (no-access)
User account is inactive.
| Step | Screen | Description |
|---|---|---|
| 1 | No Access | Informational 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>
| Parameter | Description |
|---|---|
jobId | External job ID |
token | Access 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):
- Close the sponsorship modal/iframe.
- Call
POST /ats-embed/v0/api/sponsored_statusto refresh statuses. - Update buttons and badges in your UI.
Security Appendix
Authentication
During onboarding you receive two independent credentials:
| Credential | What You Receive | Used For | How It's Sent |
|---|---|---|---|
| Jobs API Key | A single API key string | Posting, updating, and deleting jobs via the Jobs API | Authorization: Basic <job_api_key> |
| Sponsorship API Key | A single Authorization: Basic credential (encodes a client_id / client_secret pair) | Obtaining an access token for the Sponsorship API | Authorization: Basic <sponsorship_api_key> |
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-tokenSDK 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-ancestorsto 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-Optionsheader is set on embed endpoints, allowing iframe embedding on approved partner domains.
What you need to do:
- Provide all domain(s) to your ZipRecruiter partner contact, including development, testing, and production environments, so we can add them to the
frame-ancestorsCSP directive. The widget iframe will not load until your domains are whitelisted. - Serve your pages over HTTPS (required for iframe embedding).
- If your site sets its own CSP, add
frame-src https://api.ziprecruiter.comto 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
| Term | Definition |
|---|---|
external_job_id | The unique job identifier in your (partner) system. Used to correlate jobs between your ATS and ZipRecruiter. |
email | The user's email address. Passed during token exchange to identify or create the employer user. |
externalEmployerId | The unique employer identifier in your system. Must be consistent across environments. Used to link a partner employer to a ZipRecruiter organization. |
| Access Token | A short-lived Bearer token (JWT) returned by the token exchange endpoint. Valid for 2 hours. |
| Jobs API Key | A single API key string issued during onboarding. Authenticates requests to the Jobs API via Authorization: Basic header. |
| Sponsorship API Key | A 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 API | The ZipRecruiter API for posting, updating, and removing job listings (organic/unpaid). Authenticated with the Jobs API Key. |
| Sponsorship API | The API for promoting jobs with paid visibility. Authenticated via token exchange and Bearer tokens. |
| SDK | The ZipRecruiter JavaScript SDK (sdk.js) that manages widget rendering, iframe lifecycle, and event communication. |
| postMessage | The browser window.postMessage API used for cross-origin communication between the widget iframe and your ATS page. |
| CSP | Content Security Policy. An HTTP header that controls which domains can embed or be embedded by a page. |
| CORS | Cross-Origin Resource Sharing. HTTP headers that control which origins can make API requests to a server. |
| OTP | One-Time Passcode. A 6-digit code sent via email for identity verification when linking an existing account. |
Guide Changelog
| Version | Date | Description |
|---|---|---|
| V0.0.3 | 2026-06-15 | Corrected 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.2 | 2026-04-01 | Replaced sandbox/dev URLs with production URLs (https://api.ziprecruiter.com). Added explicit domain whitelisting requirements. Updated testing guidance for production environment. |
| V0.0.1 | 2026-03-03 | Updated 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. |