{"slug": "ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect", "title": "IFTTT accepted our service on the first try. The flow broke on the first redirect.", "summary": "A developer integrating the Publora social posting service with IFTTT found that IFTTT's plain OAuth2 flow, which omits PKCE, was rejected by Publora's authorization server, which requires PKCE. The developer inserted a Cloudflare Worker between the two systems to run the PKCE flow and translate between IFTTT's feed-style trigger format and Publora's API, and documented undocumented requirements including a mandatory test/setup endpoint and silently duplicated ingredient slugs.", "body_md": "Last week I was bringing Publora onto IFTTT. Our server was already live, OAuth was working, and the API was there. I expected most of the work to be filling in their application and getting through review.\n\nThe application passed on the first try, in under a day instead of the estimated one to two weeks. The actual integration broke on the first authorization redirect.\n\nHere's what I ran into.\n\nThe first problem showed up immediately.\n\nIFTTT sends plain OAuth2: authorization code, no PKCE. Our authorization server requires PKCE. Their redirect arrives without a `code_challenge`, so our server rejects it.\n\nI tried the request in exactly the format IFTTT sends:\n\n```\nGET https://mcp.publora.com/authorize\n  ?client_id=...&response_type=code&scope=ifttt&state=...&redirect_uri=...\n\n-> 302 ...?error=invalid_request\n   \"expected string, received undefined\" on code_challenge\n```\n\nAdd a `code_challenge` to the same request and it reaches the login screen normally. So there wasn't much mystery left: IFTTT doesn't send something our authorization server requires.\n\nI could either make PKCE optional on our server for IFTTT or put something between the two. I went with the second option.\n\nI added a small Cloudflare Worker between IFTTT and Publora. To IFTTT, it looks like the plain OAuth2 provider it expects: no PKCE and a non-expiring token. On the Publora side, the Worker runs the PKCE flow itself, generating the verifier and challenge before sending the request to our authorization server.\n\nThat also gave me somewhere to handle the rest of the differences between the two APIs. IFTTT expects endpoints and responses in its own format, while Publora already has its own API. The Worker translates between them.\n\nIFTTT treats a trigger as a feed rather than a current state: up to fifty recent events, newest first, each with its own `meta.id` and `meta.timestamp` in seconds.\n\nFor Publora that means one post group may need to become several events, one for each channel. Otherwise the `meta.id` isn't unique:\n\n``` js\nfor (const post of posts) {\n  for (const target of post.platforms ?? []) {\n    items.push({\n      content: post.content,\n      account: label(connections, target.platformId),\n      network: target.platform,\n      occurred_at: at,\n      meta: { id: `${post.postGroupId}:${target.platformId}`, timestamp: seconds(at) },\n    });\n  }\n}\n```\n\nThere's another requirement they test separately: every trigger needs to return at least three events during testing, so it behaves like a feed rather than a state sensor. An empty or short response doesn't pass.\n\nI also replaced hourly polling with realtime notifications. A Publora webhook is created when the connection is set up, and when an event arrives the Worker calls IFTTT's Realtime API. It doesn't send the event data there; it just tells IFTTT there's something new, and IFTTT fetches the feed itself.\n\nThis is where I spent most of the time. If you're building an IFTTT service, these are worth knowing before you start running their tests.\n\n**The `test/setup` endpoint is required, but I couldn't find it in the docs.** Their automated tests hit it second. There is a sample response in the dashboard under View scaffold JSON: it needs a `queries` section, and `media_url` can't be an empty string. The method isn't specified there, so ours accepts both GET and POST.\n\n**Ingredient slugs can double without showing it in the UI.** The form generates a slug while you're typing the field name, and entering your own can leave you with things like `contentcontent` and `occurred_atoccurred_at`. The resulting slug isn't visible in the form. I only found it in the service export under Tools, Export. A good chunk of my failed tests came from this.\n\n**The UI didn't show me why tests were failing.** I could expand the failed result row, but it was empty. The actual results were available through these endpoints:\n\n``` php\nPOST /services/<slug>/publish/endpoint_session   -> session_id\nGET  /services/<slug>/publish/endpoint_test_results?offset=N&session_id=...\n```\n\nThe results come in chunks, with `suite_end` marking the end. That's where I eventually found the individual failed checks.\n\n**A request with no fields needs to return a proper 400.** They test requests with `triggerFields` missing and with required keys missing inside it. The response needs an `errors` array. I initially supplied defaults in some of these cases, which made the tests fail.\n\n**The cursor is mandatory when results are limited.** If you return only part of the feed because of a limit, the response also needs a `cursor`. Without it, the test fails.\n\nTheir run has 535 checks. I eventually got it to zero failures, after spending considerably more time than I'd like to admit on doubled slugs and test results hidden behind an empty UI row.\n\nThere are also two launch requirements I hadn't found in the docs: twelve published applets and four service admins. You can submit the service without them, but you need them before launch.\n\nI submitted on September 7 and the review passed on September 8, so under a day against the estimated one to two weeks.\n\nThe Worker ended up at about 700 lines of TypeScript and a 19.8 KB bundle.\n\nIf you're doing an IFTTT integration, check the generated slugs in the service export rather than trusting the form. If an automated test fails without an explanation in the UI, look at `endpoint_test_results`. And before running the full suite, make sure you have `test/setup`, cursors on limited responses, and proper 400 responses for missing fields.\n\nFor OAuth, I was glad I didn't make PKCE optional in Publora just to accommodate this integration. The Worker was already needed to translate IFTTT's API format, so handling the OAuth difference there kept the change isolated too.\n\nI built it with Claude: it wrote most of the TypeScript, while I tested the requests in IFTTT's format and worked out where the two sides disagreed. Most of the time went into finding those disagreements, especially the ones I couldn't see from the dashboard.\n\nThe listing is here: [https://ifttt.com/publora](https://ifttt.com/publora)\n\nIf you've integrated with IFTTT before and found another undocumented test, I'd like to know which one.", "url": "https://wpnews.pro/news/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect", "canonical_source": "https://dev.to/eugeniya_ivanova_4a58eadc/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect-4fjd", "published_at": "2026-09-16 15:30:00+00:00", "updated_at": "2026-09-16 15:43:34.193702+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["IFTTT", "Publora", "Cloudflare Workers", "OAuth2", "PKCE"], "alternates": {"html": "https://wpnews.pro/news/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect", "markdown": "https://wpnews.pro/news/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect.md", "text": "https://wpnews.pro/news/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect.txt", "jsonld": "https://wpnews.pro/news/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect.jsonld"}}